This is a cleaned-up version of Tim’s PoC patch.
The documentation has been updated to reflect the changes.
A simple VTest test is included as well. Note the use of VTest’s cmd
feature to skip the test if the HAProxy version is lower than specified.
It might be useful for future tests as well.

Best regards
Max

Apply with `git am --scissors` to automatically cut the commit message.

-- >8 --
This patch adds the `-cc` (check condition) argument to evaluate conditions on
startup and return the result as the exit code.

As an example this can be used to easily check HAProxy's version in scripts:

    haproxy -cc 'version_atleast(2.4)'

This resolves GitHub issue #1246.

Co-authored-by: Tim Duesterhus <[email protected]>
---
 doc/configuration.txt                 |  3 ++
 doc/management.txt                    |  4 ++
 include/haproxy/cfgparse.h            |  1 +
 include/haproxy/global-t.h            |  1 +
 reg-tests/startup/check_condition.vtc | 13 ++++++
 src/cfgparse.c                        |  2 +-
 src/haproxy.c                         | 62 ++++++++++++++++++++++++++-
 7 files changed, 84 insertions(+), 2 deletions(-)
 create mode 100644 reg-tests/startup/check_condition.vtc

diff --git a/doc/configuration.txt b/doc/configuration.txt
index 6b7cc2666..e2071bd6a 100644
--- a/doc/configuration.txt
+++ b/doc/configuration.txt
@@ -802,6 +802,9 @@ Comments may be placed on the same line if needed after a 
'#', they will be
 ignored. The directives are tokenized like other configuration directives, and
 as such it is possible to use environment variables in conditions.
 
+Conditions can also be evaluated on startup with the -cc parameter.
+See "3. Starting HAProxy" in the management doc.
+
 The conditions are currently limited to:
 
   - an empty string, always returns "false"
diff --git a/doc/management.txt b/doc/management.txt
index b1ef55ec2..39c3a3160 100644
--- a/doc/management.txt
+++ b/doc/management.txt
@@ -195,6 +195,10 @@ list of options is :
     to bind. The exit status is zero if everything is OK, or non-zero if an
     error is encountered. Presence of warnings will be reported if any.
 
+  -cc : evaluates a condition as used within a conditional block of the
+    configuration. The exit status is zero if the condition is true, 1 if the
+    condition is false or 2 if an error is encountered.
+
   -d : enable debug mode. This disables daemon mode, forces the process to stay
     in foreground and to show incoming and outgoing events. It must never be
     used in an init script.
diff --git a/include/haproxy/cfgparse.h b/include/haproxy/cfgparse.h
index 1c97a88b9..51e5b9edf 100644
--- a/include/haproxy/cfgparse.h
+++ b/include/haproxy/cfgparse.h
@@ -126,6 +126,7 @@ void free_email_alert(struct proxy *p);
 const char *cfg_find_best_match(const char *word, const struct list *list, int 
section, const char **extra);
 int warnifnotcap(struct proxy *proxy, int cap, const char *file, int line, 
const char *arg, const char *hint);
 int failifnotcap(struct proxy *proxy, int cap, const char *file, int line, 
const char *arg, const char *hint);
+int cfg_eval_condition(char **args, char **err, const char **errptr);
 
 /* simplified way to define a section parser */
 #define REGISTER_CONFIG_SECTION(name, parse, post)                            \
diff --git a/include/haproxy/global-t.h b/include/haproxy/global-t.h
index 1bdbc1bbc..057d90e61 100644
--- a/include/haproxy/global-t.h
+++ b/include/haproxy/global-t.h
@@ -38,6 +38,7 @@
 #define        MODE_MWORKER_WAIT       0x100    /* Master Worker wait mode */
 #define        MODE_ZERO_WARNING       0x200    /* warnings cause a failure */
 #define        MODE_DIAG       0x400   /* extra warnings */
+#define        MODE_CHECK_CONDITION    0x800    /* -cc mode */
 
 /* list of last checks to perform, depending on config options */
 #define LSTCHK_CAP_BIND        0x00000001      /* check that we can bind to 
any port */
diff --git a/reg-tests/startup/check_condition.vtc 
b/reg-tests/startup/check_condition.vtc
new file mode 100644
index 000000000..7d3324bfe
--- /dev/null
+++ b/reg-tests/startup/check_condition.vtc
@@ -0,0 +1,13 @@
+varnishtest "Tests the -cc argument"
+
+#REQUIRE_VERSION=2.5
+feature cmd "$HAPROXY_PROGRAM -cc 'version_atleast(2.5-dev0)'"
+
+shell {
+    $HAPROXY_PROGRAM -cc "version_atleast(2.4)"
+    ! $HAPROXY_PROGRAM -cc "version_atleast(1024)"
+
+    $HAPROXY_PROGRAM -cc "streq(foo,'foo')"
+    $HAPROXY_PROGRAM -cc "streq(\"foo bar\",'foo bar')"
+    ! $HAPROXY_PROGRAM -cc "streq(foo,bar)"
+} -run
diff --git a/src/cfgparse.c b/src/cfgparse.c
index 4f2729d32..8ccf4c04e 100644
--- a/src/cfgparse.c
+++ b/src/cfgparse.c
@@ -1736,7 +1736,7 @@ static const struct cond_pred_kw 
*cfg_lookup_cond_pred(const char *str)
  * and only in this case), 0 if the condition is false, 1 if it's true. If
  * <errptr> is not NULL, it's set to the first invalid character on error.
  */
-static int cfg_eval_condition(char **args, char **err, const char **errptr)
+int cfg_eval_condition(char **args, char **err, const char **errptr)
 {
        const struct cond_pred_kw *cond_pred = NULL;
        const char *end_ptr;
diff --git a/src/haproxy.c b/src/haproxy.c
index 0e707f5ef..a56fad58c 100644
--- a/src/haproxy.c
+++ b/src/haproxy.c
@@ -570,6 +570,7 @@ static void usage(char *name)
 #endif
                "        -q quiet mode : don't display messages\n"
                "        -c check mode : only check config files and exit\n"
+               "        -cc check condition : evaluate a condition and exit\n"
                "        -n sets the maximum total # of connections (uses 
ulimit -n)\n"
                "        -m limits the usable amount of memory (in MB)\n"
                "        -N sets the default, per-proxy maximum # of 
connections (%d)\n"
@@ -1464,6 +1465,7 @@ static void init(int argc, char **argv)
        struct proxy *px;
        struct post_check_fct *pcf;
        int ideal_maxconn;
+       char *check_condition = NULL;
 
        global.mode = MODE_STARTING;
        old_argv = copy_argv(argc, argv);
@@ -1619,6 +1621,12 @@ static void init(int argc, char **argv)
                                global.tune.options |= GTUNE_RESOLVE_DONTFAIL;
                        else if (*flag == 'd')
                                arg_mode |= MODE_DEBUG;
+                       else if (*flag == 'c' && flag[1] == 'c') {
+                               arg_mode |= MODE_CHECK_CONDITION;
+                               argv++;
+                               argc--;
+                               check_condition = *argv;
+                       }
                        else if (*flag == 'c')
                                arg_mode |= MODE_CHECK;
                        else if (*flag == 'D')
@@ -1752,7 +1760,7 @@ static void init(int argc, char **argv)
 
        global.mode |= (arg_mode & (MODE_DAEMON | MODE_MWORKER | 
MODE_FOREGROUND | MODE_VERBOSE
                                    | MODE_QUIET | MODE_CHECK | MODE_DEBUG | 
MODE_ZERO_WARNING
-                                   | MODE_DIAG));
+                                   | MODE_DIAG | MODE_CHECK_CONDITION));
 
        if (getenv("HAPROXY_MWORKER_WAIT_ONLY")) {
                unsetenv("HAPROXY_MWORKER_WAIT_ONLY");
@@ -1785,6 +1793,58 @@ static void init(int argc, char **argv)
        }
 #endif
 
+       if (global.mode & MODE_CHECK_CONDITION) {
+               int result;
+
+               uint32_t err;
+               const char *errptr;
+               char *errmsg = NULL;
+
+               char *args[MAX_LINE_ARGS+1];
+               int arg = sizeof(args) / sizeof(*args);
+               size_t outlen;
+
+               err = parse_line(check_condition, check_condition, &outlen, 
args, &arg,
+                                PARSE_OPT_DQUOTE | PARSE_OPT_SQUOTE | 
PARSE_OPT_BKSLASH,
+                                &errptr);
+
+               if (err & PARSE_ERR_QUOTE) {
+                       ha_alert("Syntax Error in condition: Unmatched 
quote.\n");
+                       exit(2);
+               }
+
+               if (err & PARSE_ERR_HEX) {
+                       ha_alert("Syntax Error in condition: Truncated or 
invalid hexadecimal sequence.\n");
+                       exit(2);
+               }
+
+               if (err & (PARSE_ERR_TOOLARGE|PARSE_ERR_OVERLAP)) {
+                       ha_alert("Error in condition: Line too long.\n");
+                       exit(2);
+               }
+
+               if (err & PARSE_ERR_TOOMANY) {
+                       ha_alert("Error in condition: Too many words.\n");
+                       exit(2);
+               }
+
+               if (err) {
+                       ha_alert("Unhandled error in condition, please report 
this to the developers.\n");
+                       exit(2);
+               }
+
+               result = cfg_eval_condition(args, &errmsg, &errptr);
+
+               if (result < 0) {
+                       if (errmsg)
+                               ha_alert("Failed to evaluate condition: %s\n", 
errmsg);
+
+                       exit(2);
+               }
+
+               exit(result ? 0 : 1);
+       }
+
        /* in wait mode, we don't try to read the configuration files */
        if (!(global.mode & MODE_MWORKER_WAIT)) {
                char *env_cfgfiles = NULL;
-- 
2.25.1


Reply via email to