MEDIUM: vars: add support for a "set-var" global directive

While we do support process-wide variables ("proc.<name>"), there was
no way to preset them from the configuration. This was particularly
limiting their usefulness since configs involving them always had to
first check if the variable was set prior to performing an operation.

This patch adds a new "set-var" directive in the global section that
supports setting the proc.<name> variables from an expression, like
other set-var actions do. The syntax however follows what is already
being done for setenv, which consists in having one argument for the
variable name and another one for the expression.

Only "constant" expressions are allowed here, such as "int", "str"
etc, combined with arithmetic or string converters, and variable
lookups. A few extra sample fetch keywords like "date", "rand" and
"uuid" are also part of the constant expressions and may make sense
to allow to create a random key or differentiate processes.

The way it was done consists in parsing a dummy rule an executing the
expression in the CFG_PARSE context, then releasing the expression.
This is safe because the sample that variables store does not hold a
back pointer to expression that created them.
This commit is contained in:
Willy Tarreau 2021-03-26 11:38:08 +01:00
parent 01d580ae86
commit 13d2ba2a82
2 changed files with 86 additions and 1 deletions

View File

@ -923,6 +923,7 @@ The following keywords are supported in the "global" section :
- ulimit-n - ulimit-n
- user - user
- set-dumpable - set-dumpable
- set-var
- setenv - setenv
- stats - stats
- ssl-default-bind-ciphers - ssl-default-bind-ciphers
@ -1598,6 +1599,23 @@ server-state-file <file>
configuration. See also "server-state-base" and "show servers state", configuration. See also "server-state-base" and "show servers state",
"load-server-state-from-file" and "server-state-file-name" "load-server-state-from-file" and "server-state-file-name"
set-var <var-name> <expr>
Sets the process-wide variable '<var-name>' to the result of the evaluation
of the sample expression <expr>. The variable '<var-name>' may only be a
process-wide variable (using the 'proc.' prefix). It works exactly like the
'set-var' action in TCP or HTTP rules except that the expression is evaluated
at configuration parsing time and that the variable is instantly set. The
sample fetch functions and converters permitted in the expression are only
those using internal data, typically 'int(value)' or 'str(value)'. It's is
possible to reference previously allocated variables as well. These variables
will then be readable (and modifiable) from the regular rule sets.
Example:
global
set-var proc.current_state str(primary)
set-var proc.prio int(100)
set-var proc.threshold int(200),sub(proc.prio)
setenv <name> <value> setenv <name> <value>
Sets environment variable <name> to value <value>. If the variable exists, it Sets environment variable <name> to value <value>. If the variable exists, it
is overwritten. The changes immediately take effect so that the next line in is overwritten. The changes immediately take effect so that the next line in

View File

@ -718,7 +718,8 @@ static int conv_check_var(struct arg *args, struct sample_conv *conv,
* *
* It returns ACT_RET_PRS_ERR if fails and <err> is filled with an error * It returns ACT_RET_PRS_ERR if fails and <err> is filled with an error
* message. Otherwise, it returns ACT_RET_PRS_OK and the variable <expr> * message. Otherwise, it returns ACT_RET_PRS_OK and the variable <expr>
* is filled with the pointer to the expression to execute. * is filled with the pointer to the expression to execute. The proxy is
* only used to retrieve the ->conf entries.
*/ */
static enum act_parse_ret parse_store(const char **args, int *arg, struct proxy *px, static enum act_parse_ret parse_store(const char **args, int *arg, struct proxy *px,
struct act_rule *rule, char **err) struct act_rule *rule, char **err)
@ -799,6 +800,71 @@ static enum act_parse_ret parse_store(const char **args, int *arg, struct proxy
return ACT_RET_PRS_OK; return ACT_RET_PRS_OK;
} }
/* parses a global "set-var" directive. It will create a temporary rule and
* expression that are parsed, processed, and released on the fly so that we
* respect the real set-var syntax. These directives take the following format:
* set-var <name> <expression>
* Note that parse_store() expects "set-var(name) <expression>" so we have to
* temporarily replace the keyword here.
*/
static int vars_parse_global_set_var(char **args, int section_type, struct proxy *curpx,
const struct proxy *defpx, const char *file, int line,
char **err)
{
struct proxy px = {
.id = "CLI",
.conf.args.file = file,
.conf.args.line = line,
};
struct act_rule rule = {
.arg.vars.scope = SCOPE_PROC,
.from = ACT_F_CFG_PARSER,
};
enum act_parse_ret p_ret;
char *old_arg1;
char *tmp_arg1;
int arg = 2; // variable name
int ret = -1;
LIST_INIT(&px.conf.args.list);
if (!*args[1] || !*args[2]) {
memprintf(err, "'%s' requires a process-wide variable name ('proc.<name>') and a sample expression.", args[0]);
goto end;
}
tmp_arg1 = NULL;
if (!memprintf(&tmp_arg1, "set-var(%s)", args[1]))
goto end;
/* parse_store() will always return a message in <err> on error */
old_arg1 = args[1]; args[1] = tmp_arg1;
p_ret = parse_store((const char **)args, &arg, &px, &rule, err);
free(args[1]); args[1] = old_arg1;
if (p_ret != ACT_RET_PRS_OK)
goto end;
if (rule.arg.vars.scope != SCOPE_PROC) {
memprintf(err, "'%s': cannot set variable '%s', only scope 'proc' is permitted in the global section.", args[0], args[1]);
goto end;
}
if (smp_resolve_args(&px, err) != 0) {
release_sample_expr(rule.arg.vars.expr);
indent_msg(err, 2);
goto end;
}
action_store(&rule, &px, NULL, NULL, 0);
release_sample_expr(rule.arg.vars.expr);
ret = 0;
end:
return ret;
}
static int vars_max_size(char **args, int section_type, struct proxy *curpx, static int vars_max_size(char **args, int section_type, struct proxy *curpx,
const struct proxy *defpx, const char *file, int line, const struct proxy *defpx, const char *file, int line,
char **err, unsigned int *limit) char **err, unsigned int *limit)
@ -937,6 +1003,7 @@ static struct action_kw_list http_after_res_kws = { { }, {
INITCALL1(STG_REGISTER, http_after_res_keywords_register, &http_after_res_kws); INITCALL1(STG_REGISTER, http_after_res_keywords_register, &http_after_res_kws);
static struct cfg_kw_list cfg_kws = {{ },{ static struct cfg_kw_list cfg_kws = {{ },{
{ CFG_GLOBAL, "set-var", vars_parse_global_set_var },
{ CFG_GLOBAL, "tune.vars.global-max-size", vars_max_size_global }, { CFG_GLOBAL, "tune.vars.global-max-size", vars_max_size_global },
{ CFG_GLOBAL, "tune.vars.proc-max-size", vars_max_size_proc }, { CFG_GLOBAL, "tune.vars.proc-max-size", vars_max_size_proc },
{ CFG_GLOBAL, "tune.vars.sess-max-size", vars_max_size_sess }, { CFG_GLOBAL, "tune.vars.sess-max-size", vars_max_size_sess },