mirror of
http://git.haproxy.org/git/haproxy.git/
synced 2024-12-15 07:54:33 +00:00
MINOR: cfgparse: implement a simple if/elif/else/endif macro block handler
Very often, especially since reg-tests, it would be desirable to be able to conditionally comment out a config block, such as removing an SSL binding when SSL is disabled, or enabling HTX only for certain versions, etc. This patch introduces a very simple nested block management which takes ".if", ".elif", ".else" and ".endif" directives to take or ignore a block. For now the conditions are limited to empty string or "0" for false versus a non-nul integer for true, which already suffices to test environment variables. Still, it needs to be a bit more advanced with defines, versions etc. A set of ".notice", ".warning" and ".alert" statements are provided to emit messages, often in order to provide advice about how to fix certain conditions.
This commit is contained in:
parent
49962b58d0
commit
4b10302fd8
@ -41,8 +41,9 @@ Summary
|
||||
2.1. Configuration file format
|
||||
2.2. Quoting and escaping
|
||||
2.3. Environment variables
|
||||
2.4. Time format
|
||||
2.5. Examples
|
||||
2.4. Conditional blocks
|
||||
2.5. Time format
|
||||
2.6. Examples
|
||||
|
||||
3. Global parameters
|
||||
3.1. Process management and security
|
||||
@ -743,7 +744,75 @@ file, or could be inherited by a program (See 3.7. Programs):
|
||||
|
||||
See also "external-check command" for other variables.
|
||||
|
||||
2.4. Time format
|
||||
|
||||
2.4. Conditional blocks
|
||||
-----------------------
|
||||
|
||||
It may sometimes be convenient to be able to conditionally enable or disable
|
||||
some arbitrary parts of the configuration, for example to enable/disable SSL or
|
||||
ciphers, enable or disable some pre-production listeners without modifying the
|
||||
configuration, or adjust the configuration's syntax to support two distinct
|
||||
versions of HAProxy during a migration.. HAProxy brings a set of nestable
|
||||
preprocessor-like directives which allow to integrate or ignore some blocks of
|
||||
text. These directives must be placed on their own line and they act on the
|
||||
lines that follow them. Two of them support an expression, the other ones only
|
||||
switch to an alternate block or end a current level. The 4 following directives
|
||||
are defined to form conditional blocks:
|
||||
|
||||
- .if <condition>
|
||||
- .elif <condition>
|
||||
- .else
|
||||
- .endif
|
||||
|
||||
The ".if" directive nests a new level, ".elif" stays at the same level, ".else"
|
||||
as well, and ".endif" closes a level. Each ".if" must be terminated by a
|
||||
matching ".endif". The ".elif" may only be placed after ".if" or ".elif", and
|
||||
there is no limit to the number of ".elif" that may be chained. There may be
|
||||
only one ".else" per ".if" and it must always be after the ".if" or the last
|
||||
".elif" of a block.
|
||||
|
||||
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.
|
||||
|
||||
The conditions are currently limited to:
|
||||
|
||||
- an empty string, always returns "false"
|
||||
- the integer zero ('0'), always returns "false"
|
||||
- a non-nul integer (e.g. '1'), always returns "true".
|
||||
|
||||
Other patterns are not supported yet but the purpose is to bring a few
|
||||
functions to test for certain build options and supported features.
|
||||
|
||||
Three other directives are provided to report some status:
|
||||
|
||||
- .notice "message" : emit this message at level NOTICE
|
||||
- .warning "message" : emit this message at level WARNING
|
||||
- .alert "message" : emit this message at level ALERT
|
||||
|
||||
Messages emitted at level WARNING may cause the process to fail to start if the
|
||||
"strict-mode" is enabled. Messages emitted at level ALERT will always cause a
|
||||
fatal error. These can be used to detect some inappropriate conditions and
|
||||
provide advice to the user.
|
||||
|
||||
Example:
|
||||
|
||||
.if "${A}"
|
||||
.if "${B}"
|
||||
.notice "A=1, B=1"
|
||||
.elif "${C}"
|
||||
.notice "A=1, B=0, C=1"
|
||||
.elif "${D}"
|
||||
.warning "A=1, B=0, C=0, D=1"
|
||||
.else
|
||||
.alert "A=1, B=0, C=0, D=0"
|
||||
.endif
|
||||
.else
|
||||
.notice "A=0"
|
||||
.endif
|
||||
|
||||
|
||||
2.5. Time format
|
||||
----------------
|
||||
|
||||
Some parameters involve values representing time, such as timeouts. These
|
||||
@ -760,7 +829,7 @@ for every keyword. Supported units are :
|
||||
- d : days. 1d = 24h = 1440m = 86400s = 86400000ms
|
||||
|
||||
|
||||
2.5. Examples
|
||||
2.6. Examples
|
||||
-------------
|
||||
|
||||
# Simple configuration for an HTTP proxy listening on port 80 on all
|
||||
@ -10741,7 +10810,7 @@ stick-table type {ip | integer | string [len <length>] | binary [len <length>]}
|
||||
was last created, refreshed or matched. The expiration delay is
|
||||
defined using the standard time format, similarly as the various
|
||||
timeouts. The maximum duration is slightly above 24 days. See
|
||||
section 2.4 for more information. If this delay is not specified,
|
||||
section 2.5 for more information. If this delay is not specified,
|
||||
the session won't automatically expire, but older entries will
|
||||
be removed once full. Be sure not to use the "nopurge" parameter
|
||||
if not expiration delay is specified.
|
||||
@ -10924,7 +10993,7 @@ stick-table type {ip | integer | string [len <length>] | binary [len <length>]}
|
||||
# computed over a sliding window of 30 seconds.
|
||||
stick-table type ip size 1m expire 5m store gpc0,conn_rate(30s)
|
||||
|
||||
See also : "stick match", "stick on", "stick store-request", section 2.4
|
||||
See also : "stick match", "stick on", "stick store-request", section 2.5
|
||||
about time format and section 7 about ACLs.
|
||||
|
||||
|
||||
|
154
src/cfgparse.c
154
src/cfgparse.c
@ -98,6 +98,23 @@ struct cfg_kw_list cfg_keywords = {
|
||||
.list = LIST_HEAD_INIT(cfg_keywords.list)
|
||||
};
|
||||
|
||||
/* nested if/elif/else/endif block states */
|
||||
enum nested_cond_state {
|
||||
NESTED_COND_IF_TAKE, // "if" with a true condition
|
||||
NESTED_COND_IF_DROP, // "if" with a false condition
|
||||
NESTED_COND_IF_SKIP, // "if" masked by an outer false condition
|
||||
|
||||
NESTED_COND_ELIF_TAKE, // "elif" with a true condition from a false one
|
||||
NESTED_COND_ELIF_DROP, // "elif" with a false condition from a false one
|
||||
NESTED_COND_ELIF_SKIP, // "elif" masked by an outer false condition or a previously taken if
|
||||
|
||||
NESTED_COND_ELSE_TAKE, // taken "else" after an if false condition
|
||||
NESTED_COND_ELSE_DROP, // "else" masked by outer false condition or an if true condition
|
||||
};
|
||||
|
||||
/* 100 levels of nested conditions should already be sufficient */
|
||||
#define MAXNESTEDCONDS 100
|
||||
|
||||
/*
|
||||
* converts <str> to a list of listeners which are dynamically allocated.
|
||||
* The format is "{addr|'*'}:port[-end][,{addr|'*'}:port[-end]]*", where :
|
||||
@ -1798,6 +1815,8 @@ int readcfgfile(const char *file)
|
||||
size_t outlinesize = 0;
|
||||
int fatal = 0;
|
||||
int missing_lf = -1;
|
||||
int nested_cond_lvl = 0;
|
||||
enum nested_cond_state nested_conds[MAXNESTEDCONDS];
|
||||
|
||||
if ((thisline = malloc(sizeof(*thisline) * linesize)) == NULL) {
|
||||
ha_alert("parsing [%s] : out of memory.\n", file);
|
||||
@ -1969,6 +1988,137 @@ next_line:
|
||||
if (!**args)
|
||||
continue;
|
||||
|
||||
/* check for config macros */
|
||||
if (*args[0] == '.') {
|
||||
if (strcmp(args[0], ".if") == 0) {
|
||||
nested_cond_lvl++;
|
||||
if (nested_cond_lvl >= MAXNESTEDCONDS) {
|
||||
ha_alert("parsing [%s:%d]: too many nested '.if', max is %d.\n", file, linenum, MAXNESTEDCONDS);
|
||||
err_code |= ERR_ALERT | ERR_FATAL | ERR_ABORT;
|
||||
goto err;
|
||||
}
|
||||
|
||||
if (nested_conds[nested_cond_lvl - 1] == NESTED_COND_IF_DROP ||
|
||||
nested_conds[nested_cond_lvl - 1] == NESTED_COND_IF_SKIP ||
|
||||
nested_conds[nested_cond_lvl - 1] == NESTED_COND_ELIF_DROP ||
|
||||
nested_conds[nested_cond_lvl - 1] == NESTED_COND_ELIF_SKIP ||
|
||||
nested_conds[nested_cond_lvl - 1] == NESTED_COND_ELSE_DROP) {
|
||||
nested_conds[nested_cond_lvl] = NESTED_COND_IF_SKIP;
|
||||
} else if (!*args[1] || *args[1] == '0') {
|
||||
/* empty = false */
|
||||
nested_conds[nested_cond_lvl] = NESTED_COND_IF_DROP;
|
||||
} else if (atoi(args[1]) > 0) {
|
||||
/* true */
|
||||
nested_conds[nested_cond_lvl] = NESTED_COND_IF_TAKE;
|
||||
} else {
|
||||
ha_alert("parsing [%s:%d]: unparsable conditional expression '%s'.\n", file, linenum, args[1]);
|
||||
err_code |= ERR_ALERT | ERR_FATAL | ERR_ABORT;
|
||||
goto err;
|
||||
}
|
||||
goto next_line;
|
||||
}
|
||||
else if (strcmp(args[0], ".elif") == 0) {
|
||||
if (!nested_cond_lvl) {
|
||||
ha_alert("parsing [%s:%d]: lone '.elif' with no matching '.if'.\n", file, linenum);
|
||||
err_code |= ERR_ALERT | ERR_FATAL | ERR_ABORT;
|
||||
goto err;
|
||||
}
|
||||
|
||||
if (nested_conds[nested_cond_lvl] == NESTED_COND_ELSE_TAKE ||
|
||||
nested_conds[nested_cond_lvl] == NESTED_COND_ELSE_DROP) {
|
||||
ha_alert("parsing [%s:%d]: '.elif' after '.else' is not permitted.\n", file, linenum);
|
||||
err_code |= ERR_ALERT | ERR_FATAL | ERR_ABORT;
|
||||
goto err;
|
||||
}
|
||||
|
||||
if (nested_conds[nested_cond_lvl] == NESTED_COND_IF_TAKE ||
|
||||
nested_conds[nested_cond_lvl] == NESTED_COND_IF_SKIP ||
|
||||
nested_conds[nested_cond_lvl] == NESTED_COND_ELIF_SKIP) {
|
||||
nested_conds[nested_cond_lvl] = NESTED_COND_ELIF_SKIP;
|
||||
} else if (!*args[1] || *args[1] == '0') {
|
||||
/* empty = false */
|
||||
nested_conds[nested_cond_lvl] = NESTED_COND_ELIF_DROP;
|
||||
} else if (atoi(args[1]) > 0) {
|
||||
/* true */
|
||||
nested_conds[nested_cond_lvl] = NESTED_COND_ELIF_TAKE;
|
||||
} else {
|
||||
ha_alert("parsing [%s:%d]: unparsable conditional expression '%s'.\n", file, linenum, args[1]);
|
||||
err_code |= ERR_ALERT | ERR_FATAL | ERR_ABORT;
|
||||
goto err;
|
||||
}
|
||||
goto next_line;
|
||||
}
|
||||
else if (strcmp(args[0], ".else") == 0) {
|
||||
if (!nested_cond_lvl) {
|
||||
ha_alert("parsing [%s:%d]: lone '.else' with no matching '.if'.\n", file, linenum);
|
||||
err_code |= ERR_ALERT | ERR_FATAL | ERR_ABORT;
|
||||
goto err;
|
||||
}
|
||||
|
||||
if (nested_conds[nested_cond_lvl] == NESTED_COND_ELSE_TAKE ||
|
||||
nested_conds[nested_cond_lvl] == NESTED_COND_ELSE_DROP) {
|
||||
ha_alert("parsing [%s:%d]: '.else' after '.else' is not permitted.\n", file, linenum);
|
||||
err_code |= ERR_ALERT | ERR_FATAL | ERR_ABORT;
|
||||
goto err;
|
||||
}
|
||||
|
||||
if (nested_conds[nested_cond_lvl] == NESTED_COND_IF_TAKE ||
|
||||
nested_conds[nested_cond_lvl] == NESTED_COND_IF_SKIP ||
|
||||
nested_conds[nested_cond_lvl] == NESTED_COND_ELIF_TAKE ||
|
||||
nested_conds[nested_cond_lvl] == NESTED_COND_ELIF_SKIP) {
|
||||
nested_conds[nested_cond_lvl] = NESTED_COND_ELSE_DROP;
|
||||
} else {
|
||||
/* otherwise we take the "else" */
|
||||
nested_conds[nested_cond_lvl] = NESTED_COND_ELSE_TAKE;
|
||||
}
|
||||
goto next_line;
|
||||
}
|
||||
else if (strcmp(args[0], ".endif") == 0) {
|
||||
if (!nested_cond_lvl) {
|
||||
ha_alert("parsing [%s:%d]: lone '.endif' with no matching '.if'.\n", file, linenum);
|
||||
err_code |= ERR_ALERT | ERR_FATAL;
|
||||
fatal++;
|
||||
break;
|
||||
}
|
||||
nested_cond_lvl--;
|
||||
goto next_line;
|
||||
}
|
||||
}
|
||||
|
||||
if (nested_cond_lvl &&
|
||||
(nested_conds[nested_cond_lvl] == NESTED_COND_IF_DROP ||
|
||||
nested_conds[nested_cond_lvl] == NESTED_COND_IF_SKIP ||
|
||||
nested_conds[nested_cond_lvl] == NESTED_COND_ELIF_DROP ||
|
||||
nested_conds[nested_cond_lvl] == NESTED_COND_ELIF_SKIP ||
|
||||
nested_conds[nested_cond_lvl] == NESTED_COND_ELSE_DROP)) {
|
||||
/* The current block is masked out by the conditions */
|
||||
goto next_line;
|
||||
}
|
||||
|
||||
/* .warning/.error/.notice */
|
||||
if (*args[0] == '.') {
|
||||
if (strcmp(args[0], ".alert") == 0) {
|
||||
ha_alert("parsing [%s:%d]: '%s'.\n", file, linenum, args[1]);
|
||||
err_code |= ERR_ALERT | ERR_FATAL | ERR_ABORT;
|
||||
goto err;
|
||||
}
|
||||
else if (strcmp(args[0], ".warning") == 0) {
|
||||
ha_warning("parsing [%s:%d]: '%s'.\n", file, linenum, args[1]);
|
||||
err_code |= ERR_WARN;
|
||||
goto next_line;
|
||||
}
|
||||
else if (strcmp(args[0], ".notice") == 0) {
|
||||
ha_notice("parsing [%s:%d]: '%s'.\n", file, linenum, args[1]);
|
||||
goto next_line;
|
||||
}
|
||||
else {
|
||||
ha_alert("parsing [%s:%d]: unknown directive '%s'.\n", file, linenum, args[0]);
|
||||
err_code |= ERR_ALERT | ERR_FATAL;
|
||||
fatal++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* check for keyword modifiers "no" and "default" */
|
||||
if (strcmp(args[0], "no") == 0) {
|
||||
char *tmp;
|
||||
@ -2046,6 +2196,10 @@ next_line:
|
||||
if (cs && cs->post_section_parser)
|
||||
err_code |= cs->post_section_parser();
|
||||
|
||||
if (nested_cond_lvl) {
|
||||
ha_alert("parsing [%s:%d]: non-terminated '.if' block.\n", file, linenum);
|
||||
err_code |= ERR_ALERT | ERR_FATAL | ERR_ABORT;
|
||||
}
|
||||
err:
|
||||
free(cfg_scope);
|
||||
cfg_scope = NULL;
|
||||
|
Loading…
Reference in New Issue
Block a user