]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
MEDIUM: vars: add support for a "set-var" global directive
authorWilly Tarreau <w@1wt.eu>
Fri, 26 Mar 2021 10:38:08 +0000 (11:38 +0100)
committerWilly Tarreau <w@1wt.eu>
Fri, 26 Mar 2021 15:34:53 +0000 (16:34 +0100)
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.

doc/configuration.txt
src/vars.c

index aef9b4522d08caa27666a7f2d5bf32c4fb4a8444..0f9e31aefa5503d93ea02880e382809a7fb9b36b 100644 (file)
@@ -923,6 +923,7 @@ The following keywords are supported in the "global" section :
    - ulimit-n
    - user
    - set-dumpable
+   - set-var
    - setenv
    - stats
    - ssl-default-bind-ciphers
@@ -1598,6 +1599,23 @@ server-state-file <file>
   configuration. See also "server-state-base" and "show servers state",
   "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>
   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
index 1077a798edd1d9092516b8049f8820fa6fec142e..5c8d45e39e324ba53db3901be1fe5fe77dff985c 100644 (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
  * 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,
                                       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;
 }
 
+
+/* 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,
                          const struct proxy *defpx, const char *file, int line,
                          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);
 
 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.proc-max-size",   vars_max_size_proc   },
        { CFG_GLOBAL, "tune.vars.sess-max-size",   vars_max_size_sess   },