* 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
This commit is contained in:
Olof hagsand 2020-06-13 12:05:26 +02:00
parent e2d9c046af
commit e898dda016
16 changed files with 319 additions and 86 deletions

View file

@ -27,6 +27,11 @@ Expected: July 2020
### Major New features ### 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. * 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 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. * 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` * `--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()`. * 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: * 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() * event_reg_fd() -> clixon_event_reg_fd()

View file

@ -2,7 +2,8 @@
* *
***** BEGIN LICENSE BLOCK ***** ***** 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. This file is part of CLIXON.
@ -340,8 +341,8 @@ yang2cli_var_pattern(clicon_handle h,
} }
/* Forward */ /* Forward */
static int yang2cli_stmt(clicon_handle h, yang_stmt *ys, static int yang2cli_stmt(clicon_handle h, yang_stmt *ys, enum genmodel_type gt,
enum genmodel_type gt, int level, cbuf *cb); int level, int state, cbuf *cb);
static int yang2cli_var_union(clicon_handle h, yang_stmt *ys, char *origtype, static int yang2cli_var_union(clicon_handle h, yang_stmt *ys, char *origtype,
yang_stmt *ytype, char *helptext, cbuf *cb); yang_stmt *ytype, char *helptext, cbuf *cb);
@ -657,7 +658,7 @@ yang2cli_leaf(clicon_handle h,
*s = '\0'; *s = '\0';
} }
cprintf(cb, "%*s", level*3, ""); 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)); cprintf(cb, "%s", yang_argument_get(ys));
if (helptext) if (helptext)
cprintf(cb, "(\"%s\")", helptext); cprintf(cb, "(\"%s\")", helptext);
@ -686,6 +687,7 @@ yang2cli_leaf(clicon_handle h,
* @param[in] ys Yang statement * @param[in] ys Yang statement
* @param[in] gt CLI Generate style * @param[in] gt CLI Generate style
* @param[in] level Indentation level * @param[in] level Indentation level
* @param[in] state Include syntax for state not only config
* @param[out] cb Buffer where cligen code is written * @param[out] cb Buffer where cligen code is written
*/ */
static int static int
@ -693,6 +695,7 @@ yang2cli_container(clicon_handle h,
yang_stmt *ys, yang_stmt *ys,
enum genmodel_type gt, enum genmodel_type gt,
int level, int level,
int state,
cbuf *cb) cbuf *cb)
{ {
yang_stmt *yc; yang_stmt *yc;
@ -700,7 +703,13 @@ yang2cli_container(clicon_handle h,
int retval = -1; int retval = -1;
char *helptext = NULL; char *helptext = NULL;
char *s; char *s;
int hide = 0;
/* 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)); cprintf(cb, "%*s%s", level*3, "", yang_argument_get(ys));
if ((yd = yang_find(ys, Y_DESCRIPTION, NULL)) != NULL){ if ((yd = yang_find(ys, Y_DESCRIPTION, NULL)) != NULL){
if ((helptext = strdup(yang_argument_get(yd))) == NULL){ if ((helptext = strdup(yang_argument_get(yd))) == NULL){
@ -714,11 +723,14 @@ yang2cli_container(clicon_handle h,
if (cli_callback_generate(h, ys, cb) < 0) if (cli_callback_generate(h, ys, cb) < 0)
goto done; goto done;
cprintf(cb, ";{\n"); cprintf(cb, ";{\n");
}
yc = NULL; yc = NULL;
while ((yc = yn_each(ys, 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; goto done;
if (hide == 0)
cprintf(cb, "%*s}\n", level*3, ""); cprintf(cb, "%*s}\n", level*3, "");
retval = 0; retval = 0;
done: done:
@ -732,6 +744,7 @@ yang2cli_container(clicon_handle h,
* @param[in] ys Yang statement * @param[in] ys Yang statement
* @param[in] gt CLI Generate style * @param[in] gt CLI Generate style
* @param[in] level Indentation level * @param[in] level Indentation level
* @param[in] state Include syntax for state not only config
* @param[out] cb Buffer where cligen code is written * @param[out] cb Buffer where cligen code is written
*/ */
static int static int
@ -739,6 +752,7 @@ yang2cli_list(clicon_handle h,
yang_stmt *ys, yang_stmt *ys,
enum genmodel_type gt, enum genmodel_type gt,
int level, int level,
int state,
cbuf *cb) cbuf *cb)
{ {
yang_stmt *yc; yang_stmt *yc;
@ -775,7 +789,8 @@ yang2cli_list(clicon_handle h,
/* Print key variable now, and skip it in loop below /* Print key variable now, and skip it in loop below
Note, only print callback on last statement 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) cvec_next(cvk, cvi)?0:1, cb) < 0)
goto done; goto done;
} }
@ -794,7 +809,7 @@ yang2cli_list(clicon_handle h,
} }
if (cvi != NULL) if (cvi != NULL)
continue; continue;
if (yang2cli_stmt(h, yc, gt, level+1, cb) < 0) if (yang2cli_stmt(h, yc, gt, level+1, state, cb) < 0)
goto done; goto done;
} }
cprintf(cb, "%*s}\n", level*3, ""); cprintf(cb, "%*s}\n", level*3, "");
@ -811,6 +826,7 @@ yang2cli_list(clicon_handle h,
* @param[in] ys Yang statement * @param[in] ys Yang statement
* @param[in] gt CLI Generate style * @param[in] gt CLI Generate style
* @param[in] level Indentation level * @param[in] level Indentation level
* @param[in] state Include syntax for state not only config
* @param[out] cb Buffer where cligen code is written * @param[out] cb Buffer where cligen code is written
@example @example
choice interface-type { choice interface-type {
@ -826,6 +842,7 @@ yang2cli_choice(clicon_handle h,
yang_stmt *ys, yang_stmt *ys,
enum genmodel_type gt, enum genmodel_type gt,
int level, int level,
int state,
cbuf *cb) cbuf *cb)
{ {
int retval = -1; int retval = -1;
@ -835,7 +852,7 @@ yang2cli_choice(clicon_handle h,
while ((yc = yn_each(ys, yc)) != NULL) { while ((yc = yn_each(ys, yc)) != NULL) {
switch (yang_keyword_get(yc)){ switch (yang_keyword_get(yc)){
case Y_CASE: 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; goto done;
break; break;
case Y_CONTAINER: case Y_CONTAINER:
@ -843,7 +860,7 @@ yang2cli_choice(clicon_handle h,
case Y_LEAF_LIST: case Y_LEAF_LIST:
case Y_LIST: case Y_LIST:
default: default:
if (yang2cli_stmt(h, yc, gt, level+1, cb) < 0) if (yang2cli_stmt(h, yc, gt, level+1, state, cb) < 0)
goto done; goto done;
break; break;
} }
@ -856,32 +873,34 @@ yang2cli_choice(clicon_handle h,
/*! Generate CLI code for Yang statement /*! Generate CLI code for Yang statement
* @param[in] h Clixon handle * @param[in] h Clixon handle
* @param[in] ys Yang statement * @param[in] ys Yang statement
* @param[out] cb Buffer where cligen code is written
* @param[in] gt CLI Generate style * @param[in] gt CLI Generate style
* @param[in] level Indentation level * @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 static int
yang2cli_stmt(clicon_handle h, yang2cli_stmt(clicon_handle h,
yang_stmt *ys, yang_stmt *ys,
enum genmodel_type gt, enum genmodel_type gt,
int level, /* indentation level for pretty-print */ int level,
int state,
cbuf *cb) cbuf *cb)
{ {
yang_stmt *yc; yang_stmt *yc;
int retval = -1; int retval = -1;
if (yang_config(ys)){ if (state || yang_config(ys)){
switch (yang_keyword_get(ys)){ switch (yang_keyword_get(ys)){
case Y_CONTAINER: case Y_CONTAINER:
if (yang2cli_container(h, ys, gt, level, cb) < 0) if (yang2cli_container(h, ys, gt, level, state, cb) < 0)
goto done; goto done;
break; break;
case Y_LIST: case Y_LIST:
if (yang2cli_list(h, ys, gt, level, cb) < 0) if (yang2cli_list(h, ys, gt, level, state, cb) < 0)
goto done; goto done;
break; break;
case Y_CHOICE: case Y_CHOICE:
if (yang2cli_choice(h, ys, gt, level, cb) < 0) if (yang2cli_choice(h, ys, gt, level, state, cb) < 0)
goto done; goto done;
break; break;
case Y_LEAF_LIST: case Y_LEAF_LIST:
@ -894,7 +913,7 @@ yang2cli_stmt(clicon_handle h,
case Y_MODULE: case Y_MODULE:
yc = NULL; yc = NULL;
while ((yc = yn_each(ys, 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; goto done;
break; break;
default: /* skip */ default: /* skip */
@ -911,6 +930,7 @@ yang2cli_stmt(clicon_handle h,
* @param[in] yspec Yang specification * @param[in] yspec Yang specification
* @param[in] gt CLI Generate style * @param[in] gt CLI Generate style
* @param[in] printgen Log generated CLIgen syntax * @param[in] printgen Log generated CLIgen syntax
* @param[in] state Also include state syntax
* @param[out] ptnew CLIgen parse-tree * @param[out] ptnew CLIgen parse-tree
* *
* Code generation styles: * Code generation styles:
@ -922,6 +942,7 @@ yang2cli(clicon_handle h,
yang_stmt *yspec, yang_stmt *yspec,
enum genmodel_type gt, enum genmodel_type gt,
int printgen, int printgen,
int state,
parse_tree *ptnew) parse_tree *ptnew)
{ {
cbuf *cb = NULL; cbuf *cb = NULL;
@ -936,7 +957,7 @@ yang2cli(clicon_handle h,
/* Traverse YANG, loop through all modules and generate CLI */ /* Traverse YANG, loop through all modules and generate CLI */
ymod = NULL; ymod = NULL;
while ((ymod = yn_each(yspec, 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; goto done;
if (printgen) if (printgen)
clicon_log(LOG_NOTICE, "%s: Generated CLI spec:\n%s", __FUNCTION__, cbuf_get(cb)); clicon_log(LOG_NOTICE, "%s: Generated CLI spec:\n%s", __FUNCTION__, cbuf_get(cb));

View file

@ -2,7 +2,8 @@
* *
***** BEGIN LICENSE BLOCK ***** ***** 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. This file is part of CLIXON.
@ -40,6 +41,6 @@
* Prototypes * Prototypes
*/ */
int yang2cli(clicon_handle h, yang_stmt *yspec, enum genmodel_type gt, 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_ */ #endif /* _CLI_GENERATE_H_ */

View file

@ -547,17 +547,37 @@ main(int argc,
*/ */
if (clicon_cli_genmodel(h)){ if (clicon_cli_genmodel(h)){
parse_tree *pt = NULL; /* cli parse tree */ parse_tree *pt = NULL; /* cli parse tree */
char *treeref; cbuf *cbtreename;
parse_tree *pts = NULL; /* cli parse tree */
if ((pt = pt_new()) == NULL){ if ((pt = pt_new()) == NULL){
clicon_err(OE_UNIX, errno, "pt_new"); clicon_err(OE_UNIX, errno, "pt_new");
goto done; goto done;
} }
treeref = clicon_cli_model_treename(h); if ((cbtreename = cbuf_new()) == NULL){
/* Create cli command tree from dbspec */ clicon_err(OE_UNIX, errno, "cbuf_new");
if (yang2cli(h, yspec, clicon_cli_genmodel_type(h), printgen, pt) < 0)
goto done; 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 */ /* Initialize cli syntax */

View file

@ -421,10 +421,12 @@ show_yang(clicon_handle h,
* <format> "text"|"xml"|"json"|"cli"|"netconf" (see format_enum) * <format> "text"|"xml"|"json"|"cli"|"netconf" (see format_enum)
* <xpath> xpath expression, that may contain one %, eg "/sender[name='foo']" * <xpath> xpath expression, that may contain one %, eg "/sender[name='foo']"
* <namespace> If xpath set, the namespace the symbols in xpath belong to (optional) * <namespace> If xpath set, the namespace the symbols in xpath belong to (optional)
* <prefix> to print before cli syntax output (optional)
* @code * @code
* show config id <n:string>, cli_show_config("running","xml","iface[name='foo']","urn:example:example"); * show config id <n:string>, cli_show_config("running","xml","iface[name='foo']","urn:example:example");
* @endcode * @endcode
* @note if state parameter is set, then db must be running * @note if state parameter is set, then db must be running
* @see cli_show_auto1
*/ */
static int static int
cli_show_config1(clicon_handle h, cli_show_config1(clicon_handle h,
@ -446,9 +448,10 @@ cli_show_config1(clicon_handle h,
yang_stmt *yspec; yang_stmt *yspec;
char *namespace = NULL; char *namespace = NULL;
cvec *nsc = NULL; cvec *nsc = NULL;
char *prefix = NULL;
if (cvec_len(argv) != 3 && cvec_len(argv) != 4){ if (cvec_len(argv) < 3 && cvec_len(argv) > 5){
clicon_err(OE_PLUGIN, 0, "Got %d arguments. Expected: <dbname>,<format>,<xpath>[,<attr>]", cvec_len(argv)); clicon_err(OE_PLUGIN, 0, "Got %d arguments. Expected: <dbname>,<format>,<xpath>[,<namespace>, [<prefix>]]", cvec_len(argv));
goto done; goto done;
} }
@ -474,11 +477,14 @@ cli_show_config1(clicon_handle h,
} }
cprintf(cbxpath, "%s", xpath); cprintf(cbxpath, "%s", xpath);
/* Fourth argument is namespace */ /* Fourth argument is namespace */
if (cvec_len(argv) == 4){ if (cvec_len(argv) > 3){
namespace = cv_string_get(cvec_i(argv, 3)); namespace = cv_string_get(cvec_i(argv, 3));
if ((nsc = xml_nsctx_init(NULL, namespace)) == NULL) if ((nsc = xml_nsctx_init(NULL, namespace)) == NULL)
goto done; goto done;
} }
if (cvec_len(argv) > 4){
prefix = cv_string_get(cvec_i(argv, 4));
}
if (state == 0){ /* Get configuration-only from database */ if (state == 0){ /* Get configuration-only from database */
if (clicon_rpc_get_config(h, NULL, db, cbuf_get(cbxpath), nsc, &xt) < 0) if (clicon_rpc_get_config(h, NULL, db, cbuf_get(cbxpath), nsc, &xt) < 0)
goto done; goto done;
@ -516,7 +522,7 @@ cli_show_config1(clicon_handle h,
goto done; goto done;
xc = NULL; /* Dont print xt itself */ xc = NULL; /* Dont print xt itself */
while ((xc = xml_child_each(xt, xc, CX_ELMNT)) != NULL) 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; break;
case FORMAT_NETCONF: case FORMAT_NETCONF:
fprintf(stdout, "<rpc><edit-config><target><candidate/></target><config>\n"); fprintf(stdout, "<rpc><edit-config><target><candidate/></target><config>\n");
@ -549,6 +555,7 @@ done:
* <format> "text"|"xml"|"json"|"cli"|"netconf" (see format_enum) * <format> "text"|"xml"|"json"|"cli"|"netconf" (see format_enum)
* <xpath> xpath expression, that may contain one %, eg "/sender[name="%s"]" * <xpath> xpath expression, that may contain one %, eg "/sender[name="%s"]"
* <namespace> If xpath set, the namespace the symbols in xpath belong to (optional) * <namespace> If xpath set, the namespace the symbols in xpath belong to (optional)
* <prefix> to print before cli syntax output
* @code * @code
* show config id <n:string>, cli_show_config("running","xml","iface[name='foo']","urn:example:example"); * show config id <n:string>, cli_show_config("running","xml","iface[name='foo']","urn:example:example");
* @endcode * @endcode
@ -675,8 +682,10 @@ int cli_show_version(clicon_handle h,
* <api_path_fmt> Generated API PATH * <api_path_fmt> Generated API PATH
* <dbname> "running"|"candidate"|"startup" * <dbname> "running"|"candidate"|"startup"
* <format> "text"|"xml"|"json"|"cli"|"netconf" (see format_enum) * <format> "text"|"xml"|"json"|"cli"|"netconf" (see format_enum)
* <prefix> to print before cli syntax output
* @note if state parameter is set, then db must be running * @note if state parameter is set, then db must be running
* @note that first argument is generated by code. * @note that first argument is generated by code.
* @see cli_show_config1
*/ */
static int static int
cli_show_auto1(clicon_handle h, cli_show_auto1(clicon_handle h,
@ -687,7 +696,6 @@ cli_show_auto1(clicon_handle h,
int retval = 1; int retval = 1;
yang_stmt *yspec; yang_stmt *yspec;
char *api_path_fmt; /* xml key format */ char *api_path_fmt; /* xml key format */
// char *api_path = NULL; /* xml key */
char *db; char *db;
char *xpath = NULL; char *xpath = NULL;
cvec *nsc = NULL; cvec *nsc = NULL;
@ -698,9 +706,10 @@ cli_show_auto1(clicon_handle h,
cxobj *xerr; cxobj *xerr;
enum genmodel_type gt; enum genmodel_type gt;
char *api_path = NULL; char *api_path = NULL;
char *prefix = NULL;
if (cvec_len(argv) != 3){ if (cvec_len(argv) < 3 && cvec_len(argv) > 4){
clicon_err(OE_PLUGIN, 0, "Usage: <api-path-fmt>* <database> <format>. (*) generated."); clicon_err(OE_PLUGIN, 0, "Usage: <api-path-fmt>* <database> <format> <prefix>. (*) generated.");
goto done; goto done;
} }
/* First argv argument: API_path format */ /* First argv argument: API_path format */
@ -709,6 +718,10 @@ cli_show_auto1(clicon_handle h,
db = cv_string_get(cvec_i(argv, 1)); db = cv_string_get(cvec_i(argv, 1));
/* Third format: output format */ /* Third format: output format */
formatstr = cv_string_get(cvec_i(argv, 2)); 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){ if ((int)(format = format_str2int(formatstr)) < 0){
clicon_err(OE_PLUGIN, 0, "Not valid format: %s", formatstr); clicon_err(OE_PLUGIN, 0, "Not valid format: %s", formatstr);
goto done; goto done;
@ -758,7 +771,7 @@ cli_show_auto1(clicon_handle h,
case FORMAT_CLI: case FORMAT_CLI:
if ((gt = clicon_cli_genmodel_type(h)) == GT_ERR) if ((gt = clicon_cli_genmodel_type(h)) == GT_ERR)
goto done; goto done;
xml2cli(stdout, xp, NULL, gt); /* cli syntax */ xml2cli(stdout, xp, prefix, gt); /* cli syntax */
break; break;
case FORMAT_NETCONF: case FORMAT_NETCONF:
fprintf(stdout, "<rpc><edit-config><target><candidate/></target><config>\n"); fprintf(stdout, "<rpc><edit-config><target><candidate/></target><config>\n");
@ -786,6 +799,7 @@ cli_show_auto1(clicon_handle h,
* <api_path_fmt> Generated API PATH * <api_path_fmt> Generated API PATH
* <dbname> "running"|"candidate"|"startup" * <dbname> "running"|"candidate"|"startup"
* <format> "text"|"xml"|"json"|"cli"|"netconf" (see format_enum) * <format> "text"|"xml"|"json"|"cli"|"netconf" (see format_enum)
* <prefix> to print before cli syntax outptu
* @see cli_show_auto_state For config and state * @see cli_show_auto_state For config and state
*/ */
int int
@ -801,7 +815,9 @@ cli_show_auto(clicon_handle h,
* <api_path_fmt> Generated API PATH * <api_path_fmt> Generated API PATH
* <dbname> "running" * <dbname> "running"
* <format> "text"|"xml"|"json"|"cli"|"netconf" (see format_enum) * <format> "text"|"xml"|"json"|"cli"|"netconf" (see format_enum)
* <prefix> to print before cli syntax output
* @see cli_show_auto For config only * @see cli_show_auto For config only
* @see cli_show_config_state Not auto-generated
*/ */
int int
cli_show_auto_state(clicon_handle h, cli_show_auto_state(clicon_handle h,

View file

@ -100,6 +100,9 @@ endif
APPOBJ = $(APPSRC:.c=.o) APPOBJ = $(APPSRC:.c=.o)
# Accessible from plugin # 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 LIBSRC = restconf_lib.c
LIBOBJ = $(LIBSRC:.c=.o) LIBOBJ = $(LIBSRC:.c=.o)

View file

@ -58,20 +58,24 @@
*/ */
/*! Controls how keywords a generated in CLI syntax / prints from object model /*! Controls how keywords a generated in CLI syntax / prints from object model
* Example YANG: * Example YANG:
* container c{
* list a { * list a {
* key x; * key x;
* leaf x; * leaf x;
* leaf y; * leaf y;
* } * }
* NONE: a <x> <y>; * }
* VARS: a <x> y <y>; * NONE: c a <x> <y>;
* ALL: a x <x> y <y>; * VARS: c a <x> y <y>;
* ALL: c a x <x> y <y>;
* HIDE: a x <x> y <y>;
*/ */
enum genmodel_type{ enum genmodel_type{
GT_ERR =-1, /* Error */ GT_ERR =-1, /* Error */
GT_NONE=0, /* No extra keywords */ GT_NONE=0, /* No extra keywords */
GT_VARS, /* Keywords on non-key variables */ GT_VARS, /* Keywords on non-key variables */
GT_ALL, /* Keywords on all 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 */ /*! See clixon-config.yang type startup_mode */

View file

@ -245,6 +245,7 @@ int yang_config(yang_stmt *ys);
int yang_config_ancestor(yang_stmt *ys); int yang_config_ancestor(yang_stmt *ys);
int yang_features(clicon_handle h, yang_stmt *yt); int yang_features(clicon_handle h, yang_stmt *yt);
cvec *yang_arg2cvec(yang_stmt *ys, char *delimi); 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_key_match(yang_stmt *yn, char *name);
int yang_type_cache_regexp_set(yang_stmt *ytype, int rxmode, cvec *regexps); int yang_type_cache_regexp_set(yang_stmt *ytype, int rxmode, cvec *regexps);

View file

@ -1305,6 +1305,7 @@ netconf_module_load(clicon_handle h)
/* Load yang spec */ /* Load yang spec */
if (yang_spec_parse_module(h, "ietf-netconf", NULL, yspec)< 0) if (yang_spec_parse_module(h, "ietf-netconf", NULL, yspec)< 0)
goto done; goto done;
if (clicon_option_bool(h, "CLICON_STREAM_DISCOVERY_RFC5277"))
if (yang_spec_parse_module(h, "clixon-rfc5277", NULL, yspec)< 0) if (yang_spec_parse_module(h, "clixon-rfc5277", NULL, yspec)< 0)
goto done; goto done;
/* YANG module revision change management */ /* YANG module revision change management */

View file

@ -85,6 +85,7 @@ static const map_str2int cli_genmodel_map[] = {
{"NONE", GT_NONE}, {"NONE", GT_NONE},
{"VARS", GT_VARS}, {"VARS", GT_VARS},
{"ALL", GT_ALL}, {"ALL", GT_ALL},
{"HIDE", GT_HIDE},
{NULL, -1} {NULL, -1}
}; };

View file

@ -206,7 +206,7 @@ xml2cli(FILE *f,
if (yang_keyword_get(ys) == Y_LEAF || yang_keyword_get(ys) == Y_LEAF_LIST){ if (yang_keyword_get(ys) == Y_LEAF || yang_keyword_get(ys) == Y_LEAF_LIST){
if (prepend0) if (prepend0)
fprintf(f, "%s", 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)); fprintf(f, "%s ", xml_name(x));
if ((body = xml_body(x)) != NULL){ if ((body = xml_body(x)) != NULL){
if (index(body, ' ')) if (index(body, ' '))
@ -224,6 +224,11 @@ xml2cli(FILE *f,
} }
if (prepend0) if (prepend0)
cprintf(cbpre, "%s", prepend0); cprintf(cbpre, "%s", prepend0);
/* 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)); cprintf(cbpre, "%s ", xml_name(x));
if (yang_keyword_get(ys) == Y_LIST){ if (yang_keyword_get(ys) == Y_LIST){

View file

@ -2537,6 +2537,54 @@ yang_arg2cvec(yang_stmt *ys,
return cvv; 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 /*! Check if yang node yn has key-stmt as child which matches name
* *
* The function looks at the LIST argument string (not actual children) * The function looks at the LIST argument string (not actual children)

View file

@ -370,6 +370,8 @@ expecteq(){
# - expected stdout outcome* # - expected stdout outcome*
# - the token "--not--" # - the token "--not--"
# - not expected stdout outcome* # - 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 \[\] # @note need to escape \[\]
expectpart(){ expectpart(){
r=$? r=$?

View file

@ -1,6 +1,6 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# Tests for using the generated cli. # 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: # Tests:
# Make a config in CLI. Show output as CLI, save it and ensure it is the same # 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 cfg=$dir/conf_yang.xml
fyang=$dir/$APPNAME.yang fyang=$dir/$APPNAME.yang
fstate=$dir/state.xml
clidir=$dir/cli clidir=$dir/cli
if [ -d $clidir ]; then if [ -d $clidir ]; then
rm -rf $clidir/* rm -rf $clidir/*
@ -28,13 +29,15 @@ cat <<EOF > $cfg
<CLICON_YANG_DIR>/usr/local/share/clixon</CLICON_YANG_DIR> <CLICON_YANG_DIR>/usr/local/share/clixon</CLICON_YANG_DIR>
<CLICON_YANG_DIR>$dir</CLICON_YANG_DIR> <CLICON_YANG_DIR>$dir</CLICON_YANG_DIR>
<CLICON_YANG_MAIN_FILE>$fyang</CLICON_YANG_MAIN_FILE> <CLICON_YANG_MAIN_FILE>$fyang</CLICON_YANG_MAIN_FILE>
<CLICON_BACKEND_DIR>/usr/local/lib/$APPNAME/backend</CLICON_BACKEND_DIR>
<CLICON_CLISPEC_DIR>$clidir</CLICON_CLISPEC_DIR> <CLICON_CLISPEC_DIR>$clidir</CLICON_CLISPEC_DIR>
<CLICON_CLI_DIR>/usr/local/lib/$APPNAME/cli</CLICON_CLI_DIR> <CLICON_CLI_DIR>/usr/local/lib/$APPNAME/cli</CLICON_CLI_DIR>
<CLICON_CLI_MODE>$APPNAME</CLICON_CLI_MODE> <CLICON_CLI_MODE>$APPNAME</CLICON_CLI_MODE>
<CLICON_CLI_GENMODEL_TYPE>ALL</CLICON_CLI_GENMODEL_TYPE> <CLICON_CLI_GENMODEL_TYPE>VARS</CLICON_CLI_GENMODEL_TYPE>
<CLICON_SOCK>/usr/local/var/$APPNAME/$APPNAME.sock</CLICON_SOCK> <CLICON_SOCK>/usr/local/var/$APPNAME/$APPNAME.sock</CLICON_SOCK>
<CLICON_BACKEND_PIDFILE>/usr/local/var/$APPNAME/$APPNAME.pidfile</CLICON_BACKEND_PIDFILE> <CLICON_BACKEND_PIDFILE>/usr/local/var/$APPNAME/$APPNAME.pidfile</CLICON_BACKEND_PIDFILE>
<CLICON_XMLDB_DIR>/usr/local/var/$APPNAME</CLICON_XMLDB_DIR> <CLICON_XMLDB_DIR>/usr/local/var/$APPNAME</CLICON_XMLDB_DIR>
<CLICON_MODULE_LIBRARY_RFC7895>false</CLICON_MODULE_LIBRARY_RFC7895>
</clixon-config> </clixon-config>
EOF EOF
@ -53,9 +56,27 @@ module $APPNAME {
} }
} }
} }
container exstate{
config false;
list sender{
key ref;
leaf ref{
type string;
}
}
}
} }
EOF EOF
# This is state data written to file that backend reads from (on request)
cat <<EOF > $fstate
<exstate xmlns="urn:example:clixon">
<sender>
<ref>x</ref>
</sender>
</exstate>
EOF
cat <<EOF > $clidir/ex.cli cat <<EOF > $clidir/ex.cli
CLICON_MODE="example"; CLICON_MODE="example";
CLICON_PROMPT="%U@%H> "; CLICON_PROMPT="%U@%H> ";
@ -64,7 +85,13 @@ set @datamodel, cli_set();
merge @datamodel, cli_merge(); merge @datamodel, cli_merge();
create @datamodel, cli_create(); create @datamodel, cli_create();
delete @datamodel, cli_del(); 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 EOF
@ -75,24 +102,93 @@ if [ $BE -ne 0 ]; then
if [ $? -ne 0 ]; then if [ $? -ne 0 ]; then
err err
fi fi
new "start backend -s init -f $cfg" new "start backend -s init -f $cfg -- -sS $fstate"
start_backend -s init -f $cfg start_backend -s init -f $cfg -- -sS $fstate
new "waiting" new "waiting"
wait_backend wait_backend
fi 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 <x> y <y> or
# ALL Keywords on all variables: a x <x> y <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 "<table xmlns=\"urn:example:clixon\">" "<parameter>" "<name>a</name>" "<value>x</value>" "</parameter>" "<parameter>" "<name>b</name>" "<value>z</value>" "</parameter>" "</table>"
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" 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" new "commit"
expectfn "$clixon_cli -1 -f $cfg set table parameter name b value y" 0 "" expectpart "$($clixon_cli -1 -f $cfg commit)" 0 ""
new "set b"
expectfn "$clixon_cli -1 -f $cfg set table parameter name b value y" 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" new "Kill backend"
# Check if premature kill # Check if premature kill

View file

@ -65,7 +65,7 @@ module leafref{
} }
EOF 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 <<EOF > $fstate cat <<EOF > $fstate
<sender-state xmlns="urn:example:example"> <sender-state xmlns="urn:example:example">
<ref>x</ref> <ref>x</ref>

View file

@ -148,16 +148,19 @@ module clixon-config {
typedef cli_genmodel_type{ typedef cli_genmodel_type{
description description
"How to generate CLI from YANG model, "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{ type enumeration{
enum NONE{ enum NONE{
description "No extra keywords: a <x> <y>"; description "No extra keywords: c a <x> <y>";
} }
enum VARS{ enum VARS{
description "Keywords on non-key variables: a <x> y <y>"; description "Keywords on non-key variables: c a <x> y <y>";
} }
enum ALL{ enum ALL{
description "Keywords on all variables: a x <x> y <y>"; description "Keywords on all variables: c a x <x> y <y>";
}
enum HIDE{
description "Keywords on non-key variables and hide container around lists: a <x> y <y>";
} }
} }
} }
@ -411,7 +414,9 @@ module clixon-config {
description description
"If set, CLI specs can reference the "If set, CLI specs can reference the
model syntax using this reference. 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 { leaf CLICON_CLI_GENMODEL_COMPLETION {
type int32; type int32;
@ -595,7 +600,8 @@ module clixon-config {
description description
"If set, tag datastores with RFC 7895 YANG Module Library "If set, tag datastores with RFC 7895 YANG Module Library
info. When loaded at startup, a check is made if the system 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 { leaf CLICON_XML_CHANGELOG {
type boolean; type boolean;
@ -669,9 +675,11 @@ module clixon-config {
leaf CLICON_MODULE_LIBRARY_RFC7895 { leaf CLICON_MODULE_LIBRARY_RFC7895 {
type boolean; type boolean;
default true; default true;
description "Enable RFC 7895 YANG Module library support as state description
data. If enabled, module info will appear when doing "Enable RFC 7895 YANG Module library support as state data. If
netconf get or restconf GET"; enabled, module info will appear when doing netconf get or
restconf GET.
See also CLICON_XMLDB_MODSTATE";
} }
leaf CLICON_MODULE_SET_ID { leaf CLICON_MODULE_SET_ID {
type string; type string;