MINOR: vars/cli: add a "get var" CLI command to retrieve global variables

Process-wide variables can now be displayed from the CLI using "get var"
followed by the variable name. They must all start with "proc." otherwise
they will not be found. The output is very similar to the one of the
debug converter, with a type and value being reported for the embedded
sample.

This command is limited to clients with the level "operator" or higher,
since it can possibly expose traffic-related data.
This commit is contained in:
Willy Tarreau 2021-03-26 14:51:31 +01:00
parent 2f836de100
commit c35eb38f1d
3 changed files with 74 additions and 0 deletions

View File

@ -1770,6 +1770,12 @@ get acl <acl> <value>
type="<type>": The type of the returned sample. type="<type>": The type of the returned sample.
get var <name>
Show the existence, type and contents of the process-wide variable 'name'.
Only process-wide variables are readable, so the name must begin with
'proc.' otherwise no variable will be found. This command requires levels
"operator" or "admin".
get weight <backend>/<server> get weight <backend>/<server>
Report the current weight and the initial weight of server <server> in Report the current weight and the initial weight of server <server> in
backend <backend> or an error if either doesn't exist. The initial weight is backend <backend> or an error if either doesn't exist. The initial weight is

View File

@ -26,6 +26,11 @@ haproxy h1 -conf {
http-request return status 200 hdr x-var "proc=%[var(proc.int5)] sess=%[var(sess.int5)] req=%[var(req.int5)] str=%[var(proc.str)] uuid=%[var(proc.uuid)]" http-request return status 200 hdr x-var "proc=%[var(proc.int5)] sess=%[var(sess.int5)] req=%[var(req.int5)] str=%[var(proc.str)] uuid=%[var(proc.uuid)]"
} -start } -start
haproxy h1 -cli {
send "get var proc.int5"
expect ~ "^proc.int5: type=sint value=<5>$"
}
client c1 -connect ${h1_fe1_sock} { client c1 -connect ${h1_fe1_sock} {
txreq -req GET -url /req1_1 txreq -req GET -url /req1_1
rxresp rxresp
@ -38,6 +43,11 @@ client c1 -connect ${h1_fe1_sock} {
expect resp.http.x-var ~ "proc=10 sess=20 req=10 str=this is a string uuid=[0-9a-f]*-[0-9a-f]*-[0-9a-f]*-[0-9a-f]*-[0-9a-f]*" expect resp.http.x-var ~ "proc=10 sess=20 req=10 str=this is a string uuid=[0-9a-f]*-[0-9a-f]*-[0-9a-f]*-[0-9a-f]*-[0-9a-f]*"
} -run } -run
haproxy h1 -cli {
send "get var proc.int5"
expect ~ "^proc.int5: type=sint value=<10>$"
}
client c2 -connect ${h1_fe1_sock} { client c2 -connect ${h1_fe1_sock} {
txreq -req GET -url /req2_1 txreq -req GET -url /req2_1
rxresp rxresp
@ -49,3 +59,8 @@ client c2 -connect ${h1_fe1_sock} {
expect resp.status == 200 expect resp.status == 200
expect resp.http.x-var ~ "proc=20 sess=40 req=20 str=this is a string uuid=[0-9a-f]*-[0-9a-f]*-[0-9a-f]*-[0-9a-f]*-[0-9a-f]*" expect resp.http.x-var ~ "proc=20 sess=40 req=20 str=this is a string uuid=[0-9a-f]*-[0-9a-f]*-[0-9a-f]*-[0-9a-f]*-[0-9a-f]*"
} -run } -run
haproxy h1 -cli {
send "get var proc.int5"
expect ~ "^proc.int5: type=sint value=<20>$"
}

View File

@ -2,8 +2,10 @@
#include <haproxy/api.h> #include <haproxy/api.h>
#include <haproxy/arg.h> #include <haproxy/arg.h>
#include <haproxy/buf.h>
#include <haproxy/cfgparse.h> #include <haproxy/cfgparse.h>
#include <haproxy/check.h> #include <haproxy/check.h>
#include <haproxy/cli.h>
#include <haproxy/global.h> #include <haproxy/global.h>
#include <haproxy/http.h> #include <haproxy/http.h>
#include <haproxy/http_rules.h> #include <haproxy/http_rules.h>
@ -867,6 +869,49 @@ static int vars_parse_global_set_var(char **args, int section_type, struct proxy
return ret; return ret;
} }
/* parse CLI's "get var <name>" */
static int vars_parse_cli_get_var(char **args, char *payload, struct appctx *appctx, void *private)
{
struct vars *vars;
struct sample smp;
int i;
if (!cli_has_level(appctx, ACCESS_LVL_OPER))
return 1;
if (!*args[2])
return cli_err(appctx, "Missing process-wide variable identifier.\n");
vars = get_vars(NULL, NULL, SCOPE_PROC);
if (!vars || vars->scope != SCOPE_PROC)
return 0;
if (!vars_get_by_name(args[2], strlen(args[2]), &smp))
return cli_err(appctx, "Variable not found.\n");
/* the sample returned by vars_get_by_name() is allocated into a trash
* chunk so we have no constraint to manipulate it.
*/
chunk_printf(&trash, "%s: type=%s value=", args[2], smp_to_type[smp.data.type]);
if (!sample_casts[smp.data.type][SMP_T_STR] ||
!sample_casts[smp.data.type][SMP_T_STR](&smp)) {
chunk_appendf(&trash, "(undisplayable)");
} else {
/* Display the displayable chars*. */
b_putchr(&trash, '<');
for (i = 0; i < smp.data.u.str.data; i++) {
if (isprint((unsigned char)smp.data.u.str.area[i]))
b_putchr(&trash, smp.data.u.str.area[i]);
else
b_putchr(&trash, '.');
}
b_putchr(&trash, '>');
b_putchr(&trash, 0);
}
return cli_msg(appctx, LOG_INFO, trash.area);
}
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)
@ -1016,3 +1061,11 @@ static struct cfg_kw_list cfg_kws = {{ },{
}}; }};
INITCALL1(STG_REGISTER, cfg_register_keywords, &cfg_kws); INITCALL1(STG_REGISTER, cfg_register_keywords, &cfg_kws);
/* register cli keywords */
static struct cli_kw_list cli_kws = {{ },{
{ { "get", "var", NULL }, "get var <name> : retrieve contents of a process-wide variable", vars_parse_cli_get_var, NULL },
{ { NULL }, NULL, NULL, NULL }
}};
INITCALL1(STG_REGISTER, cli_register_kw, &cli_kws);