From e898dda0161d6e6537cc2d2f6e4c9e750ea55fb9 Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Sat, 13 Jun 2020 12:05:26 +0200 Subject: [PATCH] * Auto-CLI enhancements * A generated clispec including state (default @datanodestate) also generated along with the config clispec tree (default @datanode) * New mode `GT_HIDE` set by option `CLICON_CLI_GENMODEL_TYPE` to collapse non-presence containers that only contain a single list * Added a prfix for cli_show_config/cli_show_auto so that it can produce parseable output * Thanks dcornejo@netgate.com for trying it out and suggestions --- CHANGELOG.md | 8 +- apps/cli/cli_generate.c | 83 ++++++++----- apps/cli/cli_generate.h | 5 +- apps/cli/cli_main.c | 32 ++++- apps/cli/cli_show.c | 32 +++-- apps/restconf/Makefile.in | 3 + lib/clixon/clixon_options.h | 10 +- lib/clixon/clixon_yang.h | 1 + lib/src/clixon_netconf_lib.c | 5 +- lib/src/clixon_options.c | 1 + lib/src/clixon_xml_map.c | 9 +- lib/src/clixon_yang.c | 48 ++++++++ test/lib.sh | 2 + test/test_cli_gen.sh | 138 ++++++++++++++++++---- test/test_leafref_state.sh | 2 +- yang/clixon/clixon-config@2020-04-23.yang | 26 ++-- 16 files changed, 319 insertions(+), 86 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index eb09c81d..c08d934f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,6 +27,11 @@ Expected: July 2020 ### Major New features +* Auto-CLI enhancements + * A generated clispec including state (default @datanodestate) also generated along with the config clispec tree (default @datanode) + * New mode `GT_HIDE` set by option `CLICON_CLI_GENMODEL_TYPE` to collapse non-presence containers that only contain a single list + * Added a prfix for cli_show_config/cli_show_auto so that it can produce parseable output + * Thanks dcornejo@netgate.com for trying it out and suggestions * Embedding restconf into the existing [libevhtp](https://github.com/criticalstack/libevhtp) embedded web server. Experimental. * The existing FCGI restconf solution will continue to be supported for NGINX and other reverese proxies with an fast CGI API. * The restconf code has been refactored to support both modes. Hopefully, it should be straightforward to add another embedded server, such as GNU microhttpd. @@ -36,8 +41,9 @@ Expected: July 2020 * `--without-restconf Disable restconf altogether` -### C-API changes on existing features (For developers) +### C/CLI-API changes on existing features (For developers) +* Added prefix for cli_show_config/cli_show_auto so that it can produce parseable output * Replaced the global variable `debug` with access function: `clicon_debug_get()`. * Due to name collision with libevent, all clixon event functions prepended with `clixon_`. You need to rename your event functions as follows: * event_reg_fd() -> clixon_event_reg_fd() diff --git a/apps/cli/cli_generate.c b/apps/cli/cli_generate.c index 0e7224c1..15ef937f 100644 --- a/apps/cli/cli_generate.c +++ b/apps/cli/cli_generate.c @@ -2,7 +2,8 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2020 Olof Hagsand + Copyright (C) 2009-2019 Olof Hagsand + Copyright (C) 2020 Olof Hagsand and Rubicon Communications, LLC(Netgate) This file is part of CLIXON. @@ -340,8 +341,8 @@ yang2cli_var_pattern(clicon_handle h, } /* Forward */ -static int yang2cli_stmt(clicon_handle h, yang_stmt *ys, - enum genmodel_type gt, int level, cbuf *cb); +static int yang2cli_stmt(clicon_handle h, yang_stmt *ys, enum genmodel_type gt, + int level, int state, cbuf *cb); static int yang2cli_var_union(clicon_handle h, yang_stmt *ys, char *origtype, yang_stmt *ytype, char *helptext, cbuf *cb); @@ -657,7 +658,7 @@ yang2cli_leaf(clicon_handle h, *s = '\0'; } cprintf(cb, "%*s", level*3, ""); - if (gt == GT_VARS|| gt == GT_ALL){ + if (gt == GT_VARS|| gt == GT_ALL || gt == GT_HIDE){ cprintf(cb, "%s", yang_argument_get(ys)); if (helptext) cprintf(cb, "(\"%s\")", helptext); @@ -686,6 +687,7 @@ yang2cli_leaf(clicon_handle h, * @param[in] ys Yang statement * @param[in] gt CLI Generate style * @param[in] level Indentation level + * @param[in] state Include syntax for state not only config * @param[out] cb Buffer where cligen code is written */ static int @@ -693,6 +695,7 @@ yang2cli_container(clicon_handle h, yang_stmt *ys, enum genmodel_type gt, int level, + int state, cbuf *cb) { yang_stmt *yc; @@ -700,26 +703,35 @@ yang2cli_container(clicon_handle h, int retval = -1; char *helptext = NULL; char *s; + int hide = 0; - cprintf(cb, "%*s%s", level*3, "", yang_argument_get(ys)); - if ((yd = yang_find(ys, Y_DESCRIPTION, NULL)) != NULL){ - if ((helptext = strdup(yang_argument_get(yd))) == NULL){ - clicon_err(OE_UNIX, errno, "strdup"); - goto done; + /* If non-presence container && HIDE mode && only child is + * a list, then skip container keyword + * See also xml2cli + */ + if ((hide = yang_container_cli_hide(ys, gt)) == 0){ + cprintf(cb, "%*s%s", level*3, "", yang_argument_get(ys)); + if ((yd = yang_find(ys, Y_DESCRIPTION, NULL)) != NULL){ + if ((helptext = strdup(yang_argument_get(yd))) == NULL){ + clicon_err(OE_UNIX, errno, "strdup"); + goto done; + } + if ((s = strstr(helptext, "\n\n")) != NULL) + *s = '\0'; + cprintf(cb, "(\"%s\")", helptext); } - if ((s = strstr(helptext, "\n\n")) != NULL) - *s = '\0'; - cprintf(cb, "(\"%s\")", helptext); + if (cli_callback_generate(h, ys, cb) < 0) + goto done; + cprintf(cb, ";{\n"); } - if (cli_callback_generate(h, ys, cb) < 0) - goto done; - cprintf(cb, ";{\n"); + yc = NULL; while ((yc = yn_each(ys, yc)) != NULL) - if (yang2cli_stmt(h, yc, gt, level+1, cb) < 0) + if (yang2cli_stmt(h, yc, gt, level+1, state, cb) < 0) goto done; - cprintf(cb, "%*s}\n", level*3, ""); + if (hide == 0) + cprintf(cb, "%*s}\n", level*3, ""); retval = 0; done: if (helptext) @@ -732,6 +744,7 @@ yang2cli_container(clicon_handle h, * @param[in] ys Yang statement * @param[in] gt CLI Generate style * @param[in] level Indentation level + * @param[in] state Include syntax for state not only config * @param[out] cb Buffer where cligen code is written */ static int @@ -739,6 +752,7 @@ yang2cli_list(clicon_handle h, yang_stmt *ys, enum genmodel_type gt, int level, + int state, cbuf *cb) { yang_stmt *yc; @@ -775,7 +789,8 @@ yang2cli_list(clicon_handle h, /* Print key variable now, and skip it in loop below Note, only print callback on last statement */ - if (yang2cli_leaf(h, yleaf, gt==GT_VARS?GT_NONE:gt, level+1, + if (yang2cli_leaf(h, yleaf, + (gt==GT_VARS||gt==GT_HIDE)?GT_NONE:gt, level+1, cvec_next(cvk, cvi)?0:1, cb) < 0) goto done; } @@ -794,7 +809,7 @@ yang2cli_list(clicon_handle h, } if (cvi != NULL) continue; - if (yang2cli_stmt(h, yc, gt, level+1, cb) < 0) + if (yang2cli_stmt(h, yc, gt, level+1, state, cb) < 0) goto done; } cprintf(cb, "%*s}\n", level*3, ""); @@ -811,6 +826,7 @@ yang2cli_list(clicon_handle h, * @param[in] ys Yang statement * @param[in] gt CLI Generate style * @param[in] level Indentation level + * @param[in] state Include syntax for state not only config * @param[out] cb Buffer where cligen code is written @example choice interface-type { @@ -826,6 +842,7 @@ yang2cli_choice(clicon_handle h, yang_stmt *ys, enum genmodel_type gt, int level, + int state, cbuf *cb) { int retval = -1; @@ -835,7 +852,7 @@ yang2cli_choice(clicon_handle h, while ((yc = yn_each(ys, yc)) != NULL) { switch (yang_keyword_get(yc)){ case Y_CASE: - if (yang2cli_stmt(h, yc, gt, level+2, cb) < 0) + if (yang2cli_stmt(h, yc, gt, level+2, state, cb) < 0) goto done; break; case Y_CONTAINER: @@ -843,45 +860,47 @@ yang2cli_choice(clicon_handle h, case Y_LEAF_LIST: case Y_LIST: default: - if (yang2cli_stmt(h, yc, gt, level+1, cb) < 0) + if (yang2cli_stmt(h, yc, gt, level+1, state, cb) < 0) goto done; break; } } retval = 0; - done: + done: return retval; } /*! Generate CLI code for Yang statement * @param[in] h Clixon handle * @param[in] ys Yang statement - * @param[out] cb Buffer where cligen code is written * @param[in] gt CLI Generate style * @param[in] level Indentation level + * @param[in] state Include syntax for state not only config + * @param[out] cb Buffer where cligen code is written */ static int yang2cli_stmt(clicon_handle h, yang_stmt *ys, enum genmodel_type gt, - int level, /* indentation level for pretty-print */ + int level, + int state, cbuf *cb) { yang_stmt *yc; int retval = -1; - if (yang_config(ys)){ + if (state || yang_config(ys)){ switch (yang_keyword_get(ys)){ case Y_CONTAINER: - if (yang2cli_container(h, ys, gt, level, cb) < 0) + if (yang2cli_container(h, ys, gt, level, state, cb) < 0) goto done; break; case Y_LIST: - if (yang2cli_list(h, ys, gt, level, cb) < 0) + if (yang2cli_list(h, ys, gt, level, state, cb) < 0) goto done; break; case Y_CHOICE: - if (yang2cli_choice(h, ys, gt, level, cb) < 0) + if (yang2cli_choice(h, ys, gt, level, state, cb) < 0) goto done; break; case Y_LEAF_LIST: @@ -894,7 +913,7 @@ yang2cli_stmt(clicon_handle h, case Y_MODULE: yc = NULL; while ((yc = yn_each(ys, yc)) != NULL) - if (yang2cli_stmt(h, yc, gt, level+1, cb) < 0) + if (yang2cli_stmt(h, yc, gt, level+1, state, cb) < 0) goto done; break; default: /* skip */ @@ -902,7 +921,7 @@ yang2cli_stmt(clicon_handle h, } } retval = 0; - done: + done: return retval; } @@ -911,6 +930,7 @@ yang2cli_stmt(clicon_handle h, * @param[in] yspec Yang specification * @param[in] gt CLI Generate style * @param[in] printgen Log generated CLIgen syntax + * @param[in] state Also include state syntax * @param[out] ptnew CLIgen parse-tree * * Code generation styles: @@ -922,6 +942,7 @@ yang2cli(clicon_handle h, yang_stmt *yspec, enum genmodel_type gt, int printgen, + int state, parse_tree *ptnew) { cbuf *cb = NULL; @@ -936,7 +957,7 @@ yang2cli(clicon_handle h, /* Traverse YANG, loop through all modules and generate CLI */ ymod = NULL; while ((ymod = yn_each(yspec, ymod)) != NULL) - if (yang2cli_stmt(h, ymod, gt, 0, cb) < 0) + if (yang2cli_stmt(h, ymod, gt, 0, state, cb) < 0) goto done; if (printgen) clicon_log(LOG_NOTICE, "%s: Generated CLI spec:\n%s", __FUNCTION__, cbuf_get(cb)); diff --git a/apps/cli/cli_generate.h b/apps/cli/cli_generate.h index 84ee329c..10a4de15 100644 --- a/apps/cli/cli_generate.h +++ b/apps/cli/cli_generate.h @@ -2,7 +2,8 @@ * ***** BEGIN LICENSE BLOCK ***** - Copyright (C) 2009-2020 Olof Hagsand + Copyright (C) 2009-2019 Olof Hagsand + Copyright (C) 2020 Olof Hagsand and Rubicon Communications, LLC(Netgate) This file is part of CLIXON. @@ -40,6 +41,6 @@ * Prototypes */ int yang2cli(clicon_handle h, yang_stmt *yspec, enum genmodel_type gt, - int printgen, parse_tree *ptnew); + int printgen, int state, parse_tree *ptnew); #endif /* _CLI_GENERATE_H_ */ diff --git a/apps/cli/cli_main.c b/apps/cli/cli_main.c index 881f14d6..f93b8e2a 100644 --- a/apps/cli/cli_main.c +++ b/apps/cli/cli_main.c @@ -547,17 +547,37 @@ main(int argc, */ if (clicon_cli_genmodel(h)){ parse_tree *pt = NULL; /* cli parse tree */ - char *treeref; - + cbuf *cbtreename; + parse_tree *pts = NULL; /* cli parse tree */ + if ((pt = pt_new()) == NULL){ clicon_err(OE_UNIX, errno, "pt_new"); goto done; } - treeref = clicon_cli_model_treename(h); - /* Create cli command tree from dbspec */ - if (yang2cli(h, yspec, clicon_cli_genmodel_type(h), printgen, pt) < 0) + if ((cbtreename = cbuf_new()) == NULL){ + clicon_err(OE_UNIX, errno, "cbuf_new"); goto done; - cligen_tree_add(cli_cligen(h), treeref, pt); + } + cprintf(cbtreename, "%s", clicon_cli_model_treename(h)); + /* Create cli command tree from dbspec + * label this tree @datamodel per default + */ + if (yang2cli(h, yspec, clicon_cli_genmodel_type(h), printgen, 0, pt) < 0) + goto done; + cligen_tree_add(cli_cligen(h), cbuf_get(cbtreename), pt); + /* same for config+state + * label this tree @datamodelstate per default + */ + if ((pts = pt_new()) == NULL){ + clicon_err(OE_UNIX, errno, "pt_new"); + goto done; + } + cprintf(cbtreename, "state"); + if (yang2cli(h, yspec, clicon_cli_genmodel_type(h), 0, 1, pts) < 0) + goto done; + cligen_tree_add(cli_cligen(h), cbuf_get(cbtreename), pts); + if (cbtreename) + cbuf_free(cbtreename); } /* Initialize cli syntax */ diff --git a/apps/cli/cli_show.c b/apps/cli/cli_show.c index e8dbf71a..2ddbf623 100644 --- a/apps/cli/cli_show.c +++ b/apps/cli/cli_show.c @@ -421,10 +421,12 @@ show_yang(clicon_handle h, * "text"|"xml"|"json"|"cli"|"netconf" (see format_enum) * xpath expression, that may contain one %, eg "/sender[name='foo']" * If xpath set, the namespace the symbols in xpath belong to (optional) + * to print before cli syntax output (optional) * @code * show config id , cli_show_config("running","xml","iface[name='foo']","urn:example:example"); * @endcode * @note if state parameter is set, then db must be running + * @see cli_show_auto1 */ static int cli_show_config1(clicon_handle h, @@ -446,9 +448,10 @@ cli_show_config1(clicon_handle h, yang_stmt *yspec; char *namespace = NULL; cvec *nsc = NULL; + char *prefix = NULL; - if (cvec_len(argv) != 3 && cvec_len(argv) != 4){ - clicon_err(OE_PLUGIN, 0, "Got %d arguments. Expected: ,,[,]", cvec_len(argv)); + if (cvec_len(argv) < 3 && cvec_len(argv) > 5){ + clicon_err(OE_PLUGIN, 0, "Got %d arguments. Expected: ,,[,, []]", cvec_len(argv)); goto done; } @@ -474,11 +477,14 @@ cli_show_config1(clicon_handle h, } cprintf(cbxpath, "%s", xpath); /* Fourth argument is namespace */ - if (cvec_len(argv) == 4){ + if (cvec_len(argv) > 3){ namespace = cv_string_get(cvec_i(argv, 3)); if ((nsc = xml_nsctx_init(NULL, namespace)) == NULL) goto done; } + if (cvec_len(argv) > 4){ + prefix = cv_string_get(cvec_i(argv, 4)); + } if (state == 0){ /* Get configuration-only from database */ if (clicon_rpc_get_config(h, NULL, db, cbuf_get(cbxpath), nsc, &xt) < 0) goto done; @@ -516,7 +522,7 @@ cli_show_config1(clicon_handle h, goto done; xc = NULL; /* Dont print xt itself */ while ((xc = xml_child_each(xt, xc, CX_ELMNT)) != NULL) - xml2cli(stdout, xc, NULL, gt); /* cli syntax */ + xml2cli(stdout, xc, prefix, gt); /* cli syntax */ break; case FORMAT_NETCONF: fprintf(stdout, "\n"); @@ -549,6 +555,7 @@ done: * "text"|"xml"|"json"|"cli"|"netconf" (see format_enum) * xpath expression, that may contain one %, eg "/sender[name="%s"]" * If xpath set, the namespace the symbols in xpath belong to (optional) + * to print before cli syntax output * @code * show config id , cli_show_config("running","xml","iface[name='foo']","urn:example:example"); * @endcode @@ -675,8 +682,10 @@ int cli_show_version(clicon_handle h, * Generated API PATH * "running"|"candidate"|"startup" * "text"|"xml"|"json"|"cli"|"netconf" (see format_enum) + * to print before cli syntax output * @note if state parameter is set, then db must be running * @note that first argument is generated by code. + * @see cli_show_config1 */ static int cli_show_auto1(clicon_handle h, @@ -687,7 +696,6 @@ cli_show_auto1(clicon_handle h, int retval = 1; yang_stmt *yspec; char *api_path_fmt; /* xml key format */ - // char *api_path = NULL; /* xml key */ char *db; char *xpath = NULL; cvec *nsc = NULL; @@ -698,9 +706,10 @@ cli_show_auto1(clicon_handle h, cxobj *xerr; enum genmodel_type gt; char *api_path = NULL; + char *prefix = NULL; - if (cvec_len(argv) != 3){ - clicon_err(OE_PLUGIN, 0, "Usage: * . (*) generated."); + if (cvec_len(argv) < 3 && cvec_len(argv) > 4){ + clicon_err(OE_PLUGIN, 0, "Usage: * . (*) generated."); goto done; } /* First argv argument: API_path format */ @@ -709,6 +718,10 @@ cli_show_auto1(clicon_handle h, db = cv_string_get(cvec_i(argv, 1)); /* Third format: output format */ formatstr = cv_string_get(cvec_i(argv, 2)); + if (cvec_len(argv) > 3){ + /* Fourth format: prefix to print before cli syntax */ + prefix = cv_string_get(cvec_i(argv, 3)); + } if ((int)(format = format_str2int(formatstr)) < 0){ clicon_err(OE_PLUGIN, 0, "Not valid format: %s", formatstr); goto done; @@ -758,7 +771,7 @@ cli_show_auto1(clicon_handle h, case FORMAT_CLI: if ((gt = clicon_cli_genmodel_type(h)) == GT_ERR) goto done; - xml2cli(stdout, xp, NULL, gt); /* cli syntax */ + xml2cli(stdout, xp, prefix, gt); /* cli syntax */ break; case FORMAT_NETCONF: fprintf(stdout, "\n"); @@ -786,6 +799,7 @@ cli_show_auto1(clicon_handle h, * Generated API PATH * "running"|"candidate"|"startup" * "text"|"xml"|"json"|"cli"|"netconf" (see format_enum) + * to print before cli syntax outptu * @see cli_show_auto_state For config and state */ int @@ -801,7 +815,9 @@ cli_show_auto(clicon_handle h, * Generated API PATH * "running" * "text"|"xml"|"json"|"cli"|"netconf" (see format_enum) + * to print before cli syntax output * @see cli_show_auto For config only + * @see cli_show_config_state Not auto-generated */ int cli_show_auto_state(clicon_handle h, diff --git a/apps/restconf/Makefile.in b/apps/restconf/Makefile.in index 2804f70d..b0680280 100644 --- a/apps/restconf/Makefile.in +++ b/apps/restconf/Makefile.in @@ -100,6 +100,9 @@ endif APPOBJ = $(APPSRC:.c=.o) # Accessible from plugin +# XXX actually this does not work properly, there are functions in lib +# (eg restconf_method_notallowed) that uses symbols in restconf_api that +# are not in the lib. LIBSRC = restconf_lib.c LIBOBJ = $(LIBSRC:.c=.o) diff --git a/lib/clixon/clixon_options.h b/lib/clixon/clixon_options.h index dde7e93f..9628d9f2 100644 --- a/lib/clixon/clixon_options.h +++ b/lib/clixon/clixon_options.h @@ -58,20 +58,24 @@ */ /*! Controls how keywords a generated in CLI syntax / prints from object model * Example YANG: + * container c{ * list a { * key x; * leaf x; * leaf y; * } - * NONE: a ; - * VARS: a y ; - * ALL: a x y ; + * } + * NONE: c a ; + * VARS: c a y ; + * ALL: c a x y ; + * HIDE: a x y ; */ enum genmodel_type{ GT_ERR =-1, /* Error */ GT_NONE=0, /* No extra keywords */ GT_VARS, /* Keywords on non-key variables */ GT_ALL, /* Keywords on all variables */ + GT_HIDE, /* Keywords on all variables and hide container around lists */ }; /*! See clixon-config.yang type startup_mode */ diff --git a/lib/clixon/clixon_yang.h b/lib/clixon/clixon_yang.h index dcd758c6..d054872f 100644 --- a/lib/clixon/clixon_yang.h +++ b/lib/clixon/clixon_yang.h @@ -245,6 +245,7 @@ int yang_config(yang_stmt *ys); int yang_config_ancestor(yang_stmt *ys); int yang_features(clicon_handle h, yang_stmt *yt); cvec *yang_arg2cvec(yang_stmt *ys, char *delimi); +int yang_container_cli_hide(yang_stmt *ys, int gt); int yang_key_match(yang_stmt *yn, char *name); int yang_type_cache_regexp_set(yang_stmt *ytype, int rxmode, cvec *regexps); diff --git a/lib/src/clixon_netconf_lib.c b/lib/src/clixon_netconf_lib.c index e6d175a1..f1a789b1 100644 --- a/lib/src/clixon_netconf_lib.c +++ b/lib/src/clixon_netconf_lib.c @@ -1305,8 +1305,9 @@ netconf_module_load(clicon_handle h) /* Load yang spec */ if (yang_spec_parse_module(h, "ietf-netconf", NULL, yspec)< 0) goto done; - if (yang_spec_parse_module(h, "clixon-rfc5277", NULL, yspec)< 0) - goto done; + if (clicon_option_bool(h, "CLICON_STREAM_DISCOVERY_RFC5277")) + if (yang_spec_parse_module(h, "clixon-rfc5277", NULL, yspec)< 0) + goto done; /* YANG module revision change management */ if (clicon_option_bool(h, "CLICON_XML_CHANGELOG")) if (yang_spec_parse_module(h, "clixon-xml-changelog", NULL, yspec)< 0) diff --git a/lib/src/clixon_options.c b/lib/src/clixon_options.c index c6a45f9d..9416639d 100644 --- a/lib/src/clixon_options.c +++ b/lib/src/clixon_options.c @@ -85,6 +85,7 @@ static const map_str2int cli_genmodel_map[] = { {"NONE", GT_NONE}, {"VARS", GT_VARS}, {"ALL", GT_ALL}, + {"HIDE", GT_HIDE}, {NULL, -1} }; diff --git a/lib/src/clixon_xml_map.c b/lib/src/clixon_xml_map.c index 47ee536e..8a9a441c 100644 --- a/lib/src/clixon_xml_map.c +++ b/lib/src/clixon_xml_map.c @@ -206,7 +206,7 @@ xml2cli(FILE *f, if (yang_keyword_get(ys) == Y_LEAF || yang_keyword_get(ys) == Y_LEAF_LIST){ if (prepend0) fprintf(f, "%s", prepend0); - if (gt == GT_ALL || gt == GT_VARS) + if (gt == GT_ALL || gt == GT_VARS || gt == GT_HIDE) fprintf(f, "%s ", xml_name(x)); if ((body = xml_body(x)) != NULL){ if (index(body, ' ')) @@ -224,7 +224,12 @@ xml2cli(FILE *f, } if (prepend0) cprintf(cbpre, "%s", prepend0); - cprintf(cbpre, "%s ", xml_name(x)); + + /* If non-presence container && HIDE mode && only child is + * a list, then skip container keyword + * See also yang2cli_container */ + if (yang_container_cli_hide(ys, gt) == 0) + cprintf(cbpre, "%s ", xml_name(x)); if (yang_keyword_get(ys) == Y_LIST){ /* If list then first loop through keys */ diff --git a/lib/src/clixon_yang.c b/lib/src/clixon_yang.c index 6b333c49..db23a747 100644 --- a/lib/src/clixon_yang.c +++ b/lib/src/clixon_yang.c @@ -2537,6 +2537,54 @@ yang_arg2cvec(yang_stmt *ys, return cvv; } +/*! Check if yang is subject to generated cli GT_HIDE boolean + * The yang should be: + * 1) a non-presence container + * 2) parent of a (single) list XXX: or could multiple lists work? + * 3) no other data node children + * @retval 0 No, does not satisfy the GT_HIDE condition + * @retval 1 Yes, satisfies the GT_HIDE condition + * @see clixon-config.yang HIDE enumeration type + */ +int +yang_container_cli_hide(yang_stmt *ys, + enum genmodel_type gt) +{ + yang_stmt *yc = NULL; + int i; + enum rfc_6020 keyw; + + keyw = yang_keyword_get(ys); + /* HIDE mode */ + if (gt != GT_HIDE) + return 0; + /* A container */ + if (yang_keyword_get(ys) != Y_CONTAINER) + return 0; + /* Non-presence */ + if (yang_find(ys, Y_PRESENCE, NULL) != NULL) + return 0; + /* Ensure a single list child and no other data nodes */ + i = 0; /* Number of list nodes */ + while ((yc = yn_each(ys, yc)) != NULL) { + keyw = yang_keyword_get(yc); + /* case/choice could hide anything so disqualify those */ + if (keyw == Y_CASE || keyw == Y_CHOICE) + break; + if (!yang_datanode(yc)) /* Allowed, check next */ + continue; + if (keyw != Y_LIST) /* Another datanode than list */ + break; + if (i++>0) /* More than one list (or could this work?) */ + break; + } + if (yc != NULL) /* break from loop */ + return 0; + if (i != 1) /* List found */ + return 0; + return 1; /* Passed all tests: yes you can hide this keyword */ +} + /*! Check if yang node yn has key-stmt as child which matches name * * The function looks at the LIST argument string (not actual children) diff --git a/test/lib.sh b/test/lib.sh index fc8b5438..26b6746b 100755 --- a/test/lib.sh +++ b/test/lib.sh @@ -370,6 +370,8 @@ expecteq(){ # - expected stdout outcome* # - the token "--not--" # - not expected stdout outcome* +# Example: +# expectpart "$(a-shell-cmd arg)" 0 'expected match 1' 'expected match 2' --not-- 'not expected 1' # @note need to escape \[\] expectpart(){ r=$? diff --git a/test/test_cli_gen.sh b/test/test_cli_gen.sh index 4d78fa77..ea61433b 100755 --- a/test/test_cli_gen.sh +++ b/test/test_cli_gen.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash # Tests for using the generated cli. -# In particular setting a config, displaying as cli commands and reconfigure it using that. +# In particular setting a config, displaying as cli commands and reconfigure it # Tests: # Make a config in CLI. Show output as CLI, save it and ensure it is the same @@ -13,6 +13,7 @@ APPNAME=example cfg=$dir/conf_yang.xml fyang=$dir/$APPNAME.yang +fstate=$dir/state.xml clidir=$dir/cli if [ -d $clidir ]; then rm -rf $clidir/* @@ -28,13 +29,15 @@ cat < $cfg /usr/local/share/clixon $dir $fyang + /usr/local/lib/$APPNAME/backend $clidir /usr/local/lib/$APPNAME/cli $APPNAME - ALL + VARS /usr/local/var/$APPNAME/$APPNAME.sock /usr/local/var/$APPNAME/$APPNAME.pidfile /usr/local/var/$APPNAME + false EOF @@ -43,19 +46,37 @@ module $APPNAME { namespace "urn:example:clixon"; prefix ex; container table{ - list parameter{ - key name; - leaf name{ - type string; - } - leaf value{ - type string; - } - } + list parameter{ + key name; + leaf name{ + type string; + } + leaf value{ + type string; + } + } } + container exstate{ + config false; + list sender{ + key ref; + leaf ref{ + type string; + } + } + } } EOF +# This is state data written to file that backend reads from (on request) +cat < $fstate + + + x + + +EOF + cat < $clidir/ex.cli CLICON_MODE="example"; CLICON_PROMPT="%U@%H> "; @@ -64,7 +85,13 @@ set @datamodel, cli_set(); merge @datamodel, cli_merge(); create @datamodel, cli_create(); delete @datamodel, cli_del(); -show config @datamodel, cli_show_auto("candidate", "cli"); +show config, cli_show_config("candidate", "cli", "/", 0, "set "); +show config @datamodel, cli_show_auto("candidate", "cli", "set "); +show state, cli_show_config_state("running", "cli", "/", "set "); +show state @datamodelstate, cli_show_auto_state("running", "cli", "set "); +show xml, cli_show_config("candidate", "xml", "/"); +commit, cli_commit(); +discard, discard_changes(); EOF @@ -75,24 +102,93 @@ if [ $BE -ne 0 ]; then if [ $? -ne 0 ]; then err fi - new "start backend -s init -f $cfg" - start_backend -s init -f $cfg + new "start backend -s init -f $cfg -- -sS $fstate" + start_backend -s init -f $cfg -- -sS $fstate new "waiting" wait_backend fi -# Set a config in CLI +# Simple run trying setting a config, +# then deleting it, and reloading it +# 1. mode - either VARS Keywords on non-key variables: a y or +# ALL Keywords on all variables: a x y +testrun() +{ + mode=$1 + if [ $mode = ALL ]; then + table=" table" + name=" name" + elif [ $mode = HIDE ]; then + table= + name= + else + table=" table" + name= + fi + + new "set a" + echo "$clixon_cli -1 -o CLICON_CLI_GENMODEL_TYPE=$mode -f $cfg" + expectpart "$($clixon_cli -1 -o CLICON_CLI_GENMODEL_TYPE=$mode -f $cfg set$table parameter$name a value x)" 0 "" + + new "set b" + expectpart "$($clixon_cli -1 -o CLICON_CLI_GENMODEL_TYPE=$mode -f $cfg set$table parameter$name b value y)" 0 "" + + new "reset b" + expectpart "$($clixon_cli -1 -o CLICON_CLI_GENMODEL_TYPE=$mode -f $cfg set$table parameter$name b value z)" 0 "" + + new "show match a & b" + expectpart "$($clixon_cli -1 -o CLICON_CLI_GENMODEL_TYPE=$mode -f $cfg show config)" 0 "set$table parameter$name a" "set$table parameter$name a value x" "set$table parameter$name b" "set$table parameter$name b value z" --not-- "set$table parameter$name b value y" +SAVED=$($clixon_cli -1 -o CLICON_CLI_GENMODEL_TYPE=$mode -f $cfg show config) +# awkward having pretty-printed xml in matching strings + + new "show match a & b xml" + expectpart "$($clixon_cli -1 -o CLICON_CLI_GENMODEL_TYPE=$mode -f $cfg show xml)" 0 "" "" "a" "x" "" "" "b" "z" "" "
" + + new "delete a" + expectpart "$($clixon_cli -1 -o CLICON_CLI_GENMODEL_TYPE=$mode -f $cfg delete$table parameter$name a)" 0 "" + + new "show match b" +echo "$clixon_cli -1 -o CLICON_CLI_GENMODEL_TYPE=$mode -f $cfg show config" + expectpart "$($clixon_cli -1 -o CLICON_CLI_GENMODEL_TYPE=$mode -f $cfg show config)" 0 "$table parameter$name b" "$table parameter$name b value z" --not-- "$table parameter$name a" "$table parameter$name a value x" "$table parameter$name b value y" + + new "discard" + expectpart "$($clixon_cli -1 -o CLICON_CLI_GENMODEL_TYPE=$mode -f $cfg discard)" 0 "" + + new "show match empty" + expectpart "$($clixon_cli -1 -o CLICON_CLI_GENMODEL_TYPE=$mode -f $cfg show config)" 0 --not-- "$table parameter$name b" "$table parameter$name b value z" "$table parameter$name a" "$table parameter$name a value x" "$table parameter$name b value y" + + new "load saved cli config" + expectpart "$(echo "$SAVED" | $clixon_cli -o CLICON_CLI_GENMODEL_TYPE=$mode -f $cfg)" 0 "" + + new "show saved a & b" + expectpart "$($clixon_cli -1 -o CLICON_CLI_GENMODEL_TYPE=$mode -f $cfg show config)" 0 "set$table parameter$name a" "set$table parameter$name a value x" "set$table parameter$name b" "set$table parameter$name b value z" --not-- "set$table parameter$name b value y" + + new "discard" + expectpart "$($clixon_cli -1 -o CLICON_CLI_GENMODEL_TYPE=$mode -f $cfg discard)" 0 "" +} # testrun + +new "keywords=HIDE" +testrun HIDE + +new "keywords=ALL" +testrun ALL + +new "keywords=VARS" +testrun VARS + +# show state new "set a" -expectfn "$clixon_cli -1 -f $cfg set table parameter name a value x" 0 "" +expectpart "$($clixon_cli -1 -f $cfg set$table parameter a value x)" 0 "" -new "set b" -expectfn "$clixon_cli -1 -f $cfg set table parameter name b value y" 0 "" - -new "set b" -expectfn "$clixon_cli -1 -f $cfg set table parameter name b value y" 0 "" +new "commit" +expectpart "$($clixon_cli -1 -f $cfg commit)" 0 "" +new "show state" +expectpart "$($clixon_cli -1 -f $cfg show state)" 0 "exstate sender x" "table parameter a" "table parameter a value x" +new "show state exstate" +expectpart "$($clixon_cli -1 -f $cfg show state exstate)" 0 "state sender x" --not-- "table parameter a" "table parameter a value x" new "Kill backend" # Check if premature kill diff --git a/test/test_leafref_state.sh b/test/test_leafref_state.sh index 249656b9..7c2e5b6e 100755 --- a/test/test_leafref_state.sh +++ b/test/test_leafref_state.sh @@ -65,7 +65,7 @@ module leafref{ } EOF -# This is state data writte to file that backend reads from (on request) +# This is state data written to file that backend reads from (on request) cat < $fstate x diff --git a/yang/clixon/clixon-config@2020-04-23.yang b/yang/clixon/clixon-config@2020-04-23.yang index aa75bc91..4bee48f5 100644 --- a/yang/clixon/clixon-config@2020-04-23.yang +++ b/yang/clixon/clixon-config@2020-04-23.yang @@ -148,16 +148,19 @@ module clixon-config { typedef cli_genmodel_type{ description "How to generate CLI from YANG model, - eg list a{ key x; leaf x; leaf y;}"; + eg {container c {list a{ key x; leaf x; leaf y;}}"; type enumeration{ enum NONE{ - description "No extra keywords: a "; + description "No extra keywords: c a "; } enum VARS{ - description "Keywords on non-key variables: a y "; + description "Keywords on non-key variables: c a y "; } enum ALL{ - description "Keywords on all variables: a x y "; + description "Keywords on all variables: c a x y "; + } + enum HIDE{ + description "Keywords on non-key variables and hide container around lists: a y "; } } } @@ -411,7 +414,9 @@ module clixon-config { description "If set, CLI specs can reference the model syntax using this reference. - Example: set @datamodel, cli_set();"; + Example: set @datamodel, cli_set(); + A second tree called eg @datamodelstate is created that + also contains state together with config."; } leaf CLICON_CLI_GENMODEL_COMPLETION { type int32; @@ -595,7 +600,8 @@ module clixon-config { description "If set, tag datastores with RFC 7895 YANG Module Library info. When loaded at startup, a check is made if the system - yang modules match"; + yang modules match. + See also CLICON_MODULE_LIBRARY_RFC7895"; } leaf CLICON_XML_CHANGELOG { type boolean; @@ -669,9 +675,11 @@ module clixon-config { leaf CLICON_MODULE_LIBRARY_RFC7895 { type boolean; default true; - description "Enable RFC 7895 YANG Module library support as state - data. If enabled, module info will appear when doing - netconf get or restconf GET"; + description + "Enable RFC 7895 YANG Module library support as state data. If + enabled, module info will appear when doing netconf get or + restconf GET. + See also CLICON_XMLDB_MODSTATE"; } leaf CLICON_MODULE_SET_ID { type string;