From 5120409a5658a5af2d3a98132291430bcd2ed9cb Mon Sep 17 00:00:00 2001 From: Olof hagsand Date: Mon, 15 Jun 2020 16:07:04 +0200 Subject: [PATCH] Auto-cli updates and sync with clixon-docs --- CHANGELOG.md | 3 +- apps/cli/cli_generate.c | 15 ++- apps/cli/cli_generate.h | 12 ++ apps/cli/cli_main.c | 143 +++++++++++++++------- apps/cli/cli_plugin.c | 10 +- apps/cli/cli_show.c | 5 +- doc/FAQ.md | 7 +- example/main/example_cli.cli | 4 +- lib/src/clixon_options.c | 2 +- test/test_choice.sh | 4 +- test/test_cli.sh | 43 +++---- test/test_cli_gen.sh | 18 +-- yang/clixon/clixon-config@2020-04-23.yang | 11 +- 13 files changed, 178 insertions(+), 99 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3a0b3b9a..40fa5a2b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,7 +30,7 @@ Expected: July 2020 * 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 + * Added a prefix 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. @@ -40,7 +40,6 @@ Expected: July 2020 * `--with-restconf=evhtp Integrate restconf with libevhtp server` * `--without-restconf Disable restconf altogether` - ### 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 diff --git a/apps/cli/cli_generate.c b/apps/cli/cli_generate.c index 15ef937f..39d95289 100644 --- a/apps/cli/cli_generate.c +++ b/apps/cli/cli_generate.c @@ -63,9 +63,9 @@ #include "cli_plugin.h" #include "cli_generate.h" -/* This is the default callback function. But this is typically overwritten */ -#define GENERATE_CALLBACK "overwrite_me" - +/* + * Constants + */ /* variable expand function */ #define GENERATE_EXPAND_XMLDB "expand_dbvar" @@ -725,7 +725,6 @@ yang2cli_container(clicon_handle h, cprintf(cb, ";{\n"); } - yc = NULL; while ((yc = yn_each(ys, yc)) != NULL) if (yang2cli_stmt(h, yc, gt, level+1, state, cb) < 0) @@ -972,10 +971,10 @@ yang2cli(clicon_handle h, goto done; cvec_free(globals); /* Resolve the expand callback functions in the generated syntax. - This "should" only be GENERATE_EXPAND_XMLDB - handle=NULL for global namespace, this means expand callbacks must be in - CLICON namespace, not in a cli frontend plugin. - */ + * This "should" only be GENERATE_EXPAND_XMLDB + * handle=NULL for global namespace, this means expand callbacks must be in + * CLICON namespace, not in a cli frontend plugin. + */ if (cligen_expandv_str2fn(ptnew, (expandv_str2fn_t*)clixon_str2fn, NULL) < 0) goto done; diff --git a/apps/cli/cli_generate.h b/apps/cli/cli_generate.h index 10a4de15..819082d3 100644 --- a/apps/cli/cli_generate.h +++ b/apps/cli/cli_generate.h @@ -37,6 +37,18 @@ #ifndef _CLI_GENERATE_H_ #define _CLI_GENERATE_H_ +/* + * Constants + */ +/* This is the default "virtual" callback function of the auto-cli. It should be overwritten by + * a callback specified in a clispec, such as: + * @code + * set @datamodel, cli_set(); + * @endcode + * where the virtual callback (overwrite_me) is overwritten by cli_set. + */ +#define GENERATE_CALLBACK "overwrite_me" + /* * Prototypes */ diff --git a/apps/cli/cli_main.c b/apps/cli/cli_main.c index f93b8e2a..eb29197d 100644 --- a/apps/cli/cli_main.c +++ b/apps/cli/cli_main.c @@ -236,6 +236,103 @@ cli_interactive(clicon_handle h) return retval; } +/*! Generate one autocli clispec tree + * + * @param[in] h Clixon handle + * @param[in] name Name of tree + * @param[in] gt genmodel-type, ie HOW to generate the CLI + * @param[in] printgen Print CLI syntax to stderr + * + * Generate clispec (datamodel) from YANG dataspec and add to the set of cligen trees + * (as a separate mode) + * This tree is referenced from the main CLI spec (CLICON_CLISPEC_DIR) using the "tree reference" + * syntax, ie @datamodel + * @param[in] h Clixon handle + * @param[in] printgen Print CLI syntax generated from dbspec + * @retval 0 OK + * @retval -1 Error + * + * @note that yang2cli generates syntax for ALL modules under the loaded yangspec. + */ +static int +autocli_tree(clicon_handle h, + char *name, + enum genmodel_type gt, + int state, + int printgen) +{ + int retval = -1; + parse_tree *pt = NULL; /* cli parse tree */ + yang_stmt *yspec; + + if ((pt = pt_new()) == NULL){ + clicon_err(OE_UNIX, errno, "pt_new"); + goto done; + } + yspec = clicon_dbspec_yang(h); + /* Generate tree (this is where the action is) */ + if (yang2cli(h, yspec, gt, printgen, state, pt) < 0) + goto done; + /* Append cligen tree and name it */ + if (cligen_tree_add(cli_cligen(h), name, pt) < 0) + goto done; + retval = 0; + done: + return retval; +} + +/*! Generate autocli, ie if enabled, generate clispec from YANG and add to cligen parse-trees + * + * Generate clispec (datamodel) from YANG dataspec and add to the set of cligen trees + * (as a separate mode) + * This tree is referenced from the main CLI spec (CLICON_CLISPEC_DIR) using the "tree reference" + * syntax, ie @datamodel + * Also (if enabled) generate a second "state" tree called @datamodelstate + * + * @param[in] h Clixon handle + * @param[in] printgen Print CLI syntax generated from dbspec + * @retval 0 OK + * @retval -1 Error + */ +static int +autocli_start(clicon_handle h, + int printgen) +{ + int retval = -1; + int autocli_model = 0; + cbuf *treename = NULL; + enum genmodel_type gt; + + /* If autocli disabled quit */ + if ((autocli_model = clicon_cli_genmodel(h)) == 0) + goto ok; + /* Get the autocli type, ie HOW the cli is generated (could be much more here) */ + gt = clicon_cli_genmodel_type(h); + /* Create treename cbuf */ + if ((treename = cbuf_new()) == NULL){ + clicon_err(OE_UNIX, errno, "cbuf_new"); + goto done; + } + /* The tree name is by default @datamodel but can be changed by option (why would one do that?) */ + cprintf(treename, "%s", clicon_cli_model_treename(h)); + if (autocli_tree(h, cbuf_get(treename), gt, 0, printgen) < 0) + goto done; + + /* Create a tree for config+state. This tree's name has appended "state" to @datamodel (XXX) + */ + if (autocli_model > 1){ + cprintf(treename, "state"); + if (autocli_tree(h, cbuf_get(treename), gt, 1, printgen) < 0) + goto done; + } + ok: + retval = 0; + done: + if (treename) + cbuf_free(treename); + return retval; +} + static void usage(clicon_handle h, char *argv0) @@ -536,49 +633,9 @@ main(int argc, if (clicon_nsctx_global_set(h, nsctx_global) < 0) goto done; - /* Create tree generated from dataspec. If no other trees exists, this is - * the only one. - * The following code creates the tree @datamodel - * This tree is referenced from the main CLI spec (CLICON_CLISPEC_DIR) - * using the "tree reference" - * syntax, ie @datamodel - * But note that yang2cli generates syntax for ALL modules, not just for - * . - */ - if (clicon_cli_genmodel(h)){ - parse_tree *pt = NULL; /* cli parse tree */ - cbuf *cbtreename; - parse_tree *pts = NULL; /* cli parse tree */ - - if ((pt = pt_new()) == NULL){ - clicon_err(OE_UNIX, errno, "pt_new"); - goto done; - } - if ((cbtreename = cbuf_new()) == NULL){ - clicon_err(OE_UNIX, errno, "cbuf_new"); - goto done; - } - 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); - } + /* Create autocli from YANG */ + if (autocli_start(h, printgen) < 0) + goto done; /* Initialize cli syntax */ if (cli_syntax_load(h) < 0) diff --git a/apps/cli/cli_plugin.c b/apps/cli/cli_plugin.c index e5a956c4..0870926f 100644 --- a/apps/cli/cli_plugin.c +++ b/apps/cli/cli_plugin.c @@ -66,6 +66,7 @@ #include "clixon_cli_api.h" #include "cli_plugin.h" #include "cli_handle.h" +#include "cli_generate.h" /* * @@ -177,7 +178,7 @@ cli_syntax_unload(clicon_handle h) * @param[in] handle Handle to plugin .so module as returned by dlopen * @param[out] error Static error string, if set indicates error * @retval fn Function pointer - * @retval NULL FUnction not found or symbol NULL (check error for proper handling) + * @retval NULL Function not found or symbol NULL (check error for proper handling) * @see see cli_plugin_load where (optional) handle opened * @note the returned function is not type-checked which may result in segv at runtime */ @@ -188,8 +189,15 @@ clixon_str2fn(char *name, { void *fn = NULL; + + /* Reset error */ *error = NULL; + /* Special check for auto-cli. If the virtual callback is used, it should be overwritten later + * by a callback given in the clispec, eg: set @datamodel, cli_set(); + */ + if (strcmp(name, GENERATE_CALLBACK) == 0) + return NULL; /* First check given plugin if any */ if (handle) { diff --git a/apps/cli/cli_show.c b/apps/cli/cli_show.c index 2ddbf623..11146ba0 100644 --- a/apps/cli/cli_show.c +++ b/apps/cli/cli_show.c @@ -450,7 +450,7 @@ cli_show_config1(clicon_handle h, cvec *nsc = NULL; char *prefix = NULL; - if (cvec_len(argv) < 3 && cvec_len(argv) > 5){ + if (cvec_len(argv) < 3 || cvec_len(argv) > 5){ clicon_err(OE_PLUGIN, 0, "Got %d arguments. Expected: ,,[,, []]", cvec_len(argv)); goto done; @@ -708,7 +708,7 @@ cli_show_auto1(clicon_handle h, char *api_path = NULL; char *prefix = NULL; - if (cvec_len(argv) < 3 && cvec_len(argv) > 4){ + if (cvec_len(argv) < 3 || cvec_len(argv) > 4){ clicon_err(OE_PLUGIN, 0, "Usage: * . (*) generated."); goto done; } @@ -801,6 +801,7 @@ cli_show_auto1(clicon_handle h, * "text"|"xml"|"json"|"cli"|"netconf" (see format_enum) * to print before cli syntax outptu * @see cli_show_auto_state For config and state + * @note SHOULD be used: ... @datamodel, cli_show_auto(,...) to get correct #args */ int cli_show_auto(clicon_handle h, diff --git a/doc/FAQ.md b/doc/FAQ.md index 34c48651..2aaf2793 100644 --- a/doc/FAQ.md +++ b/doc/FAQ.md @@ -68,11 +68,11 @@ Clixon is written in C. The plugins are written in C. The CLI specification uses [CLIgen](http://github.com/olofhagsand/cligen) ## How to best understand Clixon? -Run the Clixon example, in the [example](../example) directory. +Run the Clixon main example, in the [example](../example) directory or [examples repo](https://github.com/clicon/clixon-examples), or [main documentation](https://clixon-docs.readthedocs.io) ## Hello world? -One of the examples is [a hello world example](../example/hello). Please start with that. +One of the examples is [a hello world example](https://github.com/clicon/clixon-examples/hello). Please start with that. ## How do you build and install Clixon? Clixon: @@ -128,8 +128,7 @@ clicon:x:1001:,www-data ## How do I use the CLI? -The easiest way to use Clixon is via the CLI. Once the backend is started -Example: +The easiest way to use Clixon is via the CLI. In the main example, once the backend is started you can start the auto-cli. Example: ``` clixon_cli -f /usr/local/etc/example.xml cli> set interfaces interface eth9 ? diff --git a/example/main/example_cli.cli b/example/main/example_cli.cli index 7e1a5f11..f6f1e661 100644 --- a/example/main/example_cli.cli +++ b/example/main/example_cli.cli @@ -78,8 +78,8 @@ show("Show a particular state of the system"){ xml("Show configuration as XML"), cli_show_config("candidate", "xml", "/");{ @datamodel, cli_show_auto("candidate", "xml"); } - cli("Show configuration as CLI commands"), cli_show_config("candidate", "cli", "/");{ - @datamodel, cli_show_auto("candidate", "cli"); + cli("Show configuration as CLI commands"), cli_show_config("candidate", "cli", "/", 0, "set ");{ + @datamodel, cli_show_auto("candidate", "cli", "set "); } netconf("Show configuration as netconf edit-config operation"), cli_show_config("candidate", "netconf", "/");{ @datamodel, cli_show_auto("candidate", "netconf"); diff --git a/lib/src/clixon_options.c b/lib/src/clixon_options.c index 9416639d..2bd085e1 100644 --- a/lib/src/clixon_options.c +++ b/lib/src/clixon_options.c @@ -613,7 +613,7 @@ clicon_option_del(clicon_handle h, * But sometimes there are type conversions, etc which makes it more * convenient to make wrapper functions. Or not? *-----------------------------------------------------------------*/ -/*! Wether to generate CLIgen syntax from datamodel or not (0 or 1) +/*! Whether to generate CLIgen syntax from datamodel or not (0, 1 or 2) * Must be used with a previous clicon_option_exists(). * @param[in] h Clicon handle * @retval flag If set, generate CLI code from yang model, otherwise not diff --git a/test/test_choice.sh b/test/test_choice.sh index 433e296c..a75a7bfd 100755 --- a/test/test_choice.sh +++ b/test/test_choice.sh @@ -189,13 +189,13 @@ new "cli set protocol udp" expectfn "$clixon_cli -1 -f $cfg -l o set system protocol udp" 0 "^$" new "cli get protocol udp" -expectfn "$clixon_cli -1 -f $cfg -l o show configuration cli " 0 "^system protocol udp$" +expectfn "$clixon_cli -1 -f $cfg -l o show configuration cli " 0 "^set system protocol udp$" new "cli change protocol to tcp" expectfn "$clixon_cli -1 -f $cfg -l o set system protocol tcp" 0 "^$" new "cli get protocol tcp" -expectfn "$clixon_cli -1 -f $cfg -l o show configuration cli " 0 "^system protocol tcp$" +expectfn "$clixon_cli -1 -f $cfg -l o show configuration cli " 0 "^set system protocol tcp$" new "cli delete all" expectfn "$clixon_cli -1 -f $cfg -l o delete all" 0 "^$" diff --git a/test/test_cli.sh b/test/test_cli.sh index 6a088e15..c033deb7 100755 --- a/test/test_cli.sh +++ b/test/test_cli.sh @@ -49,24 +49,25 @@ if [ $BE -ne 0 ]; then fi new "cli configure top" -expectfn "$clixon_cli -1 -f $cfg set interfaces" 0 "^$" +expectpart "$($clixon_cli -1 -f $cfg set interfaces)" 0 "^$" new "cli show configuration top (no presence)" -expectfn "$clixon_cli -1 -f $cfg show conf cli" 0 "^$" +expectpart "$($clixon_cli -1 -f $cfg show conf cli)" 0 "^$" new "cli configure delete top" -expectfn "$clixon_cli -1 -f $cfg delete interfaces" 0 "^$" +expectpart "$($clixon_cli -1 -f $cfg delete interfaces)" 0 "^$" new "cli show configuration delete top" -expectfn "$clixon_cli -1 -f $cfg show conf cli" 0 "^$" +expectpart "$($clixon_cli -1 -f $cfg show conf cli)" 0 "^$" new "cli configure set interfaces" -expectfn "$clixon_cli -1 -f $cfg set interfaces interface eth/0/0" 0 "^$" +expectpart "$($clixon_cli -1 -f $cfg set interfaces interface eth/0/0)" 0 "^$" new "cli show configuration" -expectfn "$clixon_cli -1 -f $cfg show conf cli" 0 '^interfaces interface eth/0/0 interfaces interface eth/0/0 enabled true' +expectpart "$($clixon_cli -1 -f $cfg show conf cli)" 0 "^set interfaces interface eth/0/0" "^set interfaces interface eth/0/0 enabled true" new "cli configure using encoded chars data <&" +# problems in changing to expectpart with escapes expectfn "$clixon_cli -1 -f $cfg set interfaces interface eth/0/0 description \"foo<&bar\"" 0 "" new "cli configure using encoded chars name <&" @@ -76,53 +77,53 @@ new "cli failed validate" expectpart "$($clixon_cli -1 -f $cfg -l o validate)" 255 "Validate failed. Edit and try again or discard changes: application missing-element Mandatory variable type" new "cli configure ip addr" -expectfn "$clixon_cli -1 -f $cfg set interfaces interface eth/0/0 ipv4 address 1.2.3.4 prefix-length 24" 0 "^$" +expectpart "$($clixon_cli -1 -f $cfg set interfaces interface eth/0/0 ipv4 address 1.2.3.4 prefix-length 24)" 0 "^$" new "cli configure ip descr" -expectfn "$clixon_cli -1 -f $cfg set interfaces interface eth/0/0 description mydesc" 0 "^$" +expectpart "$($clixon_cli -1 -f $cfg set interfaces interface eth/0/0 description mydesc)" 0 "^$" new "cli configure ip type" -expectfn "$clixon_cli -1 -f $cfg set interfaces interface eth/0/0 type ex:eth" 0 "^$" +expectpart "$($clixon_cli -1 -f $cfg set interfaces interface eth/0/0 type ex:eth)" 0 "^$" new "cli show xpath description" -expectfn "$clixon_cli -1 -f $cfg -l o show xpath /interfaces/interface/description urn:ietf:params:xml:ns:yang:ietf-interfaces" 0 "mydesc" +expectpart "$($clixon_cli -1 -f $cfg -l o show xpath /interfaces/interface/description urn:ietf:params:xml:ns:yang:ietf-interfaces)" 0 "mydesc" new "cli delete description" -expectfn "$clixon_cli -1 -f $cfg -l o delete interfaces interface eth/0/0 description mydesc" 0 "" +expectpart "$($clixon_cli -1 -f $cfg -l o delete interfaces interface eth/0/0 description mydesc)" 0 "" new "cli show xpath no description" -expectfn "$clixon_cli -1 -f $cfg -l o show xpath /interfaces/interface/description urn:ietf:params:xml:ns:yang:ietf-interfaces" 0 "^$" +expectpart "$($clixon_cli -1 -f $cfg -l o show xpath /interfaces/interface/description urn:ietf:params:xml:ns:yang:ietf-interfaces)" 0 "^$" new "cli copy interface" -expectfn "$clixon_cli -1 -f $cfg copy interface eth/0/0 to eth99" 0 "^$" +expectpart "$($clixon_cli -1 -f $cfg copy interface eth/0/0 to eth99)" 0 "^$" new "cli success validate" -expectfn "$clixon_cli -1 -f $cfg -l o validate" 0 "^$" +expectpart "$($clixon_cli -1 -f $cfg -l o validate)" 0 "^$" new "cli commit" -expectfn "$clixon_cli -1 -f $cfg -l o commit" 0 "^$" +expectpart "$($clixon_cli -1 -f $cfg -l o commit)" 0 "^$" new "cli save" -expectfn "$clixon_cli -1 -f $cfg -l o save /tmp/foo" 0 "^$" +expectpart "$($clixon_cli -1 -f $cfg -l o save /tmp/foo)" 0 "^$" new "cli delete all" -expectfn "$clixon_cli -1 -f $cfg -l o delete all" 0 "^$" +expectpart "$($clixon_cli -1 -f $cfg -l o delete all)" 0 "^$" new "cli load" -expectfn "$clixon_cli -1 -f $cfg -l o load /tmp/foo" 0 "^$" +expectpart "$($clixon_cli -1 -f $cfg -l o load /tmp/foo)" 0 "^$" new "cli check load" -expectfn "$clixon_cli -1 -f $cfg -l o show conf cli" 0 "interfaces interface eth/0/0 ipv4 enabled true" +expectpart "$($clixon_cli -1 -f $cfg -l o show conf cli)" 0 "interfaces interface eth/0/0 ipv4 enabled true" new "cli debug set" -expectfn "$clixon_cli -1 -f $cfg -l o debug level 1" 0 "^$" +expectpart "$($clixon_cli -1 -f $cfg -l o debug level 1)" 0 "^$" # How to test this? new "cli debug reset" expectfn "$clixon_cli -1 -f $cfg -l o debug level 0" 0 "^$" new "cli rpc" -expectfn "$clixon_cli -1 -f $cfg -l o rpc ipv4" 0 'ipv442' +expectpart "$($clixon_cli -1 -f $cfg -l o rpc ipv4)" 0 'ipv442' if [ $BE -eq 0 ]; then exit # BE diff --git a/test/test_cli_gen.sh b/test/test_cli_gen.sh index ea61433b..c95245f3 100755 --- a/test/test_cli_gen.sh +++ b/test/test_cli_gen.sh @@ -33,6 +33,7 @@ cat < $cfg $clidir /usr/local/lib/$APPNAME/cli $APPNAME + 2 VARS /usr/local/var/$APPNAME/$APPNAME.sock /usr/local/var/$APPNAME/$APPNAME.pidfile @@ -57,14 +58,14 @@ module $APPNAME { } } container exstate{ - config false; - list sender{ - key ref; - leaf ref{ - type string; - } - } - } + config false; + list sender{ + key ref; + leaf ref{ + type string; + } + } + } } EOF @@ -90,6 +91,7 @@ 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", "/"); +show xml @datamodel, cli_show_auto("candidate", "xml"); commit, cli_commit(); discard, discard_changes(); diff --git a/yang/clixon/clixon-config@2020-04-23.yang b/yang/clixon/clixon-config@2020-04-23.yang index 4bee48f5..ecad2d21 100644 --- a/yang/clixon/clixon-config@2020-04-23.yang +++ b/yang/clixon/clixon-config@2020-04-23.yang @@ -402,11 +402,12 @@ module clixon-config { type int32; default 1; description - "If set, generate CLI specification for CLI completion of - loaded Yang modules. This CLI tree can be accessed in CLI - spec files using the tree reference syntax (eg @datamodel). - See also CLICON_CLI_MODEL_TREENAME. - (consider boolean)"; + "0: Do not generate CLISPEC syntax for the auto-cli. + 1: Generate a CLI specification for CLI completion of all loaded Yang modules. + This CLI tree can be accessed in CLI-spec files using the tree reference syntax (eg + @datamodel). + 2: Same including state syntax in a tree called @datamodelstate. + See also CLICON_CLI_MODEL_TREENAME."; } leaf CLICON_CLI_MODEL_TREENAME { type string;