From: Christopher Faulet Date: Wed, 30 Sep 2026 16:10:22 +0000 (+0200) Subject: MINOR: filters: Expose classes and instances through the CLI X-Git-Tag: v3.5-dev9~85 X-Git-Url: https://git.kaiwu.me/$%7BGITURL%7D$%7Bcid%7D?a=commitdiff_plain;h=649eaa3db4d6bba403ebeafa29689eb0faa0658e;p=haproxy.git MINOR: filters: Expose classes and instances through the CLI Add operator-level commands to inspect the request/response class order, list a proxy's flat instance order, and inspect a single instance by class and optional id. Display its enabled and implicit/inherited state, source location, sides and raw arguments. Keep these diagnostics separate from the configuration and ordering changes. They report the new instance model, not the still-legacy stream evaluation path. This completes the split of the current WIP without changing its final source contents or completing runtime integration. --- diff --git a/src/filters.c b/src/filters.c index 376f24c09..20d12c3da 100644 --- a/src/filters.c +++ b/src/filters.c @@ -13,6 +13,8 @@ #include #include #include +#include +#include #include #include #include @@ -21,6 +23,7 @@ #include #include #include +#include #include #include #include @@ -2737,6 +2740,212 @@ static int flt_precheck_instances_all() return 0; } +/* Dumps the request classes. The classes inserted in the before/after lists are + * inserted around the class they are attached to. + */ +static void cli_dump_flt_req_classes(struct buffer *out, struct list *classes) +{ + struct filter_class *cls; + int first = 1; + + list_for_each_entry(cls, classes, req.list) { + if (!first) + chunk_appendf(out, " > "); + if (!LIST_ISEMPTY(&cls->req.before)) { + cli_dump_flt_req_classes(out, &cls->req.before); + chunk_appendf(out, " > "); + } + chunk_appendf(out, cls->name); + if (!LIST_ISEMPTY(&cls->req.after)) { + chunk_appendf(out, " > "); + cli_dump_flt_req_classes(out, &cls->req.after); + } + first = 0; + } + chunk_appendf(&trash, "\n"); +} + +/* Dumps the response classes. The classes inserted in the before/after lists are + * inserted around the class they are attached to. + */ +static void cli_dump_flt_res_classes(struct buffer *out, struct list *classes) +{ + struct filter_class *cls; + int first = 1; + + list_for_each_entry(cls, classes, res.list) { + if (!first) + chunk_appendf(out, " > "); + if (!LIST_ISEMPTY(&cls->res.before)) { + cli_dump_flt_res_classes(out, &cls->res.before); + chunk_appendf(out, " > "); + } + chunk_appendf(out, cls->name); + if (!LIST_ISEMPTY(&cls->res.after)) { + chunk_appendf(out, " > "); + cli_dump_flt_res_classes(out, &cls->res.after); + } + first = 0; + } + chunk_appendf(&trash, "\n"); +} + +/* Parses "show filter classes [request|response]" */ +static int cli_parse_show_flt_classes(char **args, char *payload, struct appctx *appctx, void *private) +{ + if (!cli_has_level(appctx, ACCESS_LVL_OPER)) + return 1; + + chunk_reset(&trash); + + if (!*args[3] || strcmp(args[3], "request") == 0) { + chunk_appendf(&trash, "request: "); + cli_dump_flt_req_classes(&trash, &req_filter_classes); + } + if (!*args[3] || strcmp(args[3], "response") == 0) { + chunk_appendf(&trash, "response: "); + cli_dump_flt_res_classes(&trash, &res_filter_classes); + } + if (*args[3] && strcmp(args[3], "request") != 0 && strcmp(args[3], "response") != 0) + return cli_err(appctx, "'request' or 'response' expected\n"); + + return cli_msg(appctx, LOG_INFO, trash.area); +} + + + +/* Dumps the filter instance as "[:]", prefixed by " > " + * unless is set. + */ +static void cli_dump_flat_flt_def(struct buffer *out, struct filter_instance *inst, int *first) +{ + chunk_appendf(out, "%s%s", *first ? "" : " > ", inst->class->name); + if (inst->id) + chunk_appendf(out, ":%s", inst->id); + *first = 0; +} + +/* Parse "show filter instances [request|response]". Displays the filter + * instances of the proxy , one line per side (both sides if no side is + * selected). + */ +static int cli_parse_show_flt_instances(char **args, char *payload, struct appctx *appctx, void *private) +{ + struct proxy *px; + struct filter_instance *inst; + int first = 1; + + if (!cli_has_level(appctx, ACCESS_LVL_OPER)) + return 1; + + if (!*args[3]) + return cli_err(appctx, "a proxy name is expected\n"); + + px = proxy_find_by_name(args[3], 0, 0); + if (!px) + px = proxy_find_by_name(args[3], PR_CAP_DEF, 0); + if (!px) + return cli_err(appctx, "unknown proxy\n"); + + if (*args[4] && strcmp(args[4], "request") != 0 && strcmp(args[4], "response") != 0) + return cli_err(appctx, "'request' or 'response' expected\n"); + + chunk_reset(&trash); + + if (!*args[4] || strcmp(args[4], "request") == 0) { + chunk_appendf(&trash, "request: "); + list_for_each_entry(inst, &px->filter_req_instances, req.list) + cli_dump_flat_flt_def(&trash, inst, &first); + } + if (!*args[4] || strcmp(args[4], "response") == 0) { + chunk_appendf(&trash, "response: "); + list_for_each_entry(inst, &px->filter_res_instances, res.list) + cli_dump_flat_flt_def(&trash, inst, &first); + } + + if (first) + chunk_appendf(&trash, ""); + chunk_appendf(&trash, "\n"); + + return cli_msg(appctx, LOG_INFO, trash.area); +} + +/* Parse "show filter instance ". Displays all the + * information of a single filter instance of the proxy . + */ +static int cli_parse_show_flt_instance(char **args, char *payload, struct appctx *appctx, void *private) +{ + struct filter_instance *found = NULL; + struct filter_class *cls; + struct proxy *px; + char *id = NULL; + int count = 0; + int i; + + if (!cli_has_level(appctx, ACCESS_LVL_OPER)) + return 1; + + if (!*args[3] || !*args[4]) + return cli_err(appctx, "a proxy name and a instance (class[/id]) are expected\n"); + + px = proxy_find_by_name(args[3], 0, 0); + if (!px) + px = proxy_find_by_name(args[3], PR_CAP_DEF, 0); + if (!px) + return cli_err(appctx, "unknown proxy\n"); + + /* split the class[/id] argument (args are strdup'ed, it can be + * modified in place) + */ + id = strchr(args[4], '/'); + if (id) + *id++ = '\0'; + + cls = filter_find_class(args[4]); + if (cls) + found = flt_find_instance_count(px, cls, id, &count); + else + return cli_err(appctx, "unknown filter class\n"); + + if (id && !found) + return cli_err(appctx, "unknown instance\n"); + if (!id && count > 1) + return cli_err(appctx, "several instances of this class, an id is required\n"); + if (!found) + return cli_err(appctx, "no instance of this class for this proxy\n"); + + chunk_reset(&trash); + chunk_appendf(&trash, "class: %s\n", found->class->name); + chunk_appendf(&trash, "id: %s\n", found->id ? found->id : ""); + chunk_appendf(&trash, "state: %s%s%s\n", + found->enabled ? "enabled" : "disabled", + (found->flags & FLT_INST_F_IMPLICIT) ? ", implicit" : "", + (found->flags & FLT_INST_F_INHERITED) ? ", inherited" : ""); + chunk_appendf(&trash, "sides: %s%s\n", + LIST_INLIST(&found->class->req.list) ? " request" : "", + LIST_INLIST(&found->class->res.list) ? " response" : ""); + chunk_appendf(&trash, "defined at: %s:%d\n", found->conf.file, found->conf.line); + chunk_appendf(&trash, "args: "); + if (found->conf.argc) { + for (i = 0; i < found->conf.argc; i++) + chunk_appendf(&trash, " %s", found->conf.argv[i]); + } + else + chunk_appendf(&trash, " "); + chunk_appendf(&trash, "\n"); + + return cli_msg(appctx, LOG_INFO, trash.area); +} + +static struct cli_kw_list cli_kws = {ILH, { + {{ "show", "filter", "classes", NULL }, "show filter classes [request|response] : display all filter classes", cli_parse_show_flt_classes, NULL, NULL, NULL }, + {{ "show", "filter", "instance", NULL }, "show filter instance : display a filter instance of a proxy", cli_parse_show_flt_instance, NULL, NULL, NULL }, + {{ "show", "filter", "instances", NULL }, "show filter instances [side] : display the filter instances of a proxy", cli_parse_show_flt_instances, NULL, NULL, NULL }, + {{},} +}}; + +INITCALL1(STG_REGISTER, cli_register_kw, &cli_kws); + /* Checks that all registered filter classes are placed on at least one * side. The classes are placed with filter_place_class() (see * filter_init_classes() for the internal classes), so a class without