diff --git a/CHANGELOG.md b/CHANGELOG.md index 8b56a876..2cb0ab41 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,6 +29,12 @@ Expected: 15 October 2020 ### New features +* New YANG generated auto-cli feature with syntax modes + * The existing autocli does not support modes, complete paths must be given, eg: `set a b c d 42`. + * In the new auto-cli, automatic modes are present at each YANG syntax node level, eg the above can be given as: `edit a b c; set d 4; top` + * The existing CLI API remains, the new API is as follows: `cli_auto_edit()`, `cli_auto_up()`, `cli_auto_top()`, `cli_auto_show()`, `cli_auto_set()`, `cli_auto_merge()`, `cli_auto_create()`, `cli_auto_del()`. + * See `test/test_cli_auto.sh` for an example of the new API, and `apps/cli/cli_auto.c` for the source code of the new callback API. + * Documentation will be appear and full integration with the main example. * Added support for the following XPATH functions: * `count`, `name`, `contains`, `not`, as defined in [xpath 1.0](https://www.w3.org/TR/xpath-10) * `deref`, `derived-from` and `derived-from-or-self` from RFC7950 Section 10. @@ -54,8 +60,10 @@ Users may have to change how they access the system * Not implemented XPath functions will cause a backend exit on startup, instead of being ignored. * More explanatory validation error messages for when and augments error messages. * Example: error-message: `Mandatory variable` -> `Mandatory variable of edit-config in module ietf-netconf`. + ### Minor changes +* Added inline state field to clixon-example.yang * Added stricter check on schema-node identifier checking, such as for augments. * These checks are now made at YANG loading time * Added sanity check that a yang module name matches the filename diff --git a/apps/cli/Makefile.in b/apps/cli/Makefile.in index edeb00c7..1813309d 100644 --- a/apps/cli/Makefile.in +++ b/apps/cli/Makefile.in @@ -82,6 +82,7 @@ LIBSRC = cli_common.c LIBSRC += cli_show.c LIBSRC += cli_handle.c LIBSRC += cli_plugin.c +LIBSRC += cli_auto.c LIBOBJ = $(LIBSRC:.c=.o) # Name of lib diff --git a/apps/cli/cli_auto.c b/apps/cli/cli_auto.c new file mode 100644 index 00000000..ed7f54f3 --- /dev/null +++ b/apps/cli/cli_auto.c @@ -0,0 +1,560 @@ +/* + * + ***** BEGIN LICENSE BLOCK ***** + + Copyright (C) 2009-2016 Olof Hagsand and Benny Holmgren + Copyright (C) 2017-2019 Olof Hagsand + Copyright (C) 2020 Olof Hagsand and Rubicon Communications, LLC(Netgate) + + This file is part of CLIXON. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + Alternatively, the contents of this file may be used under the terms of + the GNU General Public License Version 3 or later (the "GPL"), + in which case the provisions of the GPL are applicable instead + of those above. If you wish to allow use of your version of this file only + under the terms of the GPL, and not to allow others to + use your version of this file under the terms of Apache License version 2, + indicate your decision by deleting the provisions above and replace them with + the notice and other provisions required by the GPL. If you do not delete + the provisions above, a recipient may use your version of this file under + the terms of any one of the Apache License version 2 or the GPL. + + ***** END LICENSE BLOCK ***** + * Autocli mode support + */ + +#ifdef HAVE_CONFIG_H +#include "clixon_config.h" /* generated by config & autoconf */ +#endif + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* cligen */ +#include + +/* clicon */ +#include + +#include "clixon_cli_api.h" +#include "cli_common.h" + +/* + * CLIXON CLI parse-tree workpoint API: Essentially a mirror of the cligen_wp_set() and similar functions + */ +static char * +co2apipath(cg_obj *co) +{ + struct cg_callback *cb; + cvec *cvv; + cg_var *cv; + + if (co == NULL) + return NULL; + if ((cb = co->co_callbacks) == NULL) + return NULL; + if ((cvv = cb->cc_cvec) == NULL) + return NULL; + if ((cv = cvec_i(cvv, 0)) == NULL) + return NULL; + return cv_string_get(cv);; +} + +static cvec* +cvec_append(cvec *cvv0, + cvec *cvv1) +{ + cvec *cvv2 = NULL; + cg_var *cv; + + if (cvv0 == NULL){ + if ((cvv2 = cvec_dup(cvv1)) == NULL){ + clicon_err(OE_UNIX, errno, "cvec_dup"); + return NULL; + } + } + else{ + if ((cvv2 = cvec_dup(cvv0)) == NULL){ + clicon_err(OE_UNIX, errno, "cvec_dup"); + return NULL; + } + cv = NULL; /* Append cvv1 to cvv2 */ + while ((cv = cvec_each1(cvv1, cv)) != NULL) + cvec_append_var(cvv2, cv); + } + return cvv2; +} + +/*! Enter a CLI edit mode + * @param[in] h CLICON handle + * @param[in] cvv Vector of variables from CLIgen command-line + * @param[in] argv Vector oif user-supplied keywords + * Format of argv: + * Generated API PATH (This is where we are in the tree) + * Name of generated cligen parse-tree, eg "datamodel" + * "running"|"candidate"|"startup"y + */ +int +cli_auto_edit(clicon_handle h, + cvec *cvv1, + cvec *argv) +{ + int retval = -1; + char *api_path_fmt; /* xml key format */ + char *api_path = NULL; + cg_var *cv; + char *treename; + pt_head *ph; + cg_obj *co; + cg_obj *coorig; + cligen_handle ch; + cvec *cvv2 = NULL; /* cvv2 = cvv0 + cvv1 */ + + cv = cvec_i(argv, 1); + treename = cv_string_get(cv); + ch = cli_cligen(h); + if ((ph = cligen_ph_find(cli_cligen(h), treename)) == NULL){ + clicon_err(OE_PLUGIN, 0, "No such parsetree header: %s", treename); + goto done; + } + if ((co = cligen_co_match(ch)) != NULL && + (coorig = co->co_treeref_orig) != NULL) + cligen_ph_workpoint_set(ph, coorig); + else{ + clicon_err(OE_YANG, EINVAL, "No workpoint found"); + goto done; + } + if ((cvv2 = cvec_append(clicon_data_cvec_get(h, "cli-edit-cvv"), cvv1)) == NULL) + goto done; + fprintf(stderr, "%s alloc %p\n", __FUNCTION__, cvv2); + /* First argv argument: API_path format */ + if ((api_path_fmt = co2apipath(coorig)) == NULL){ + clicon_err(OE_YANG, EINVAL, "No apipath found"); + goto done; + } + /* get api-path and xpath */ + if (api_path_fmt2api_path(api_path_fmt, cvv2, &api_path) < 0) + goto done; + /* Store this as edit-mode */ + clicon_data_set(h, "cli-edit-mode", api_path); + if (clicon_data_cvec_set(h, "cli-edit-cvv", cvv2) < 0) + goto done; + retval = 0; + done: + if (api_path) + free(api_path); + return retval; +} + +/*! CLI callback: Working point tree up to parent + * @param[in] h CLICON handle + * @param[in] cvv Vector of variables from CLIgen command-line + * @param[in] argv Vector oif user-supplied keywords + * Format of argv: + * Name of generated cligen parse-tree, eg "datamodel" + * "running"|"candidate"|"startup" + */ +int +cli_auto_up(clicon_handle h, + cvec *cvv, + cvec *argv) +{ + int retval = -1; + cg_var *cv; + char *treename; + cg_obj *co0 = NULL; /* from */ + cg_obj *co1 = NULL; /* to (parent, or several parent steps) */ + pt_head *ph; + cvec *cvv0 = NULL; + cvec *cvv1 = NULL; /* copy */ + char *api_path_fmt0; /* from */ + char *api_path_fmt1; /* to */ + char *api_path; + int i; + int j; + + cv = cvec_i(argv, 0); + treename = cv_string_get(cv); + if ((ph = cligen_ph_find(cli_cligen(h), treename)) == NULL){ + clicon_err(OE_PLUGIN, 0, "No such parsetree header: %s", treename); + goto done; + } + if ((co0 = cligen_ph_workpoint_get(ph)) == NULL) + goto ok; + co1 = co_up(co0); + /* Find parent that has a callback */ + while (co1 && (co1->co_callbacks == NULL)) + co1 = co_up(co1); + cligen_ph_workpoint_set(ph, co1); + if (co1 == NULL){ + clicon_data_set(h, "cli-edit-mode", ""); + clicon_data_cvec_del(h, "cli-edit-cvv"); + goto ok; + } + /* get before and after api-path-fmt (as generated from yang) */ + api_path_fmt0 = co2apipath(co0); + api_path_fmt1 = co2apipath(co1); + assert(strlen(api_path_fmt0) > strlen(api_path_fmt1)); + /* Find diff of 0 and 1 (how many variables differ?) and trunc cvv0 by that amount */ + cvv0 = clicon_data_cvec_get(h, "cli-edit-cvv"); + j=0; /* count diffs */ + for (i=strlen(api_path_fmt1); i Name of generated cligen parse-tree, eg "datamodel" + * "running"|"candidate"|"startup" + */ +int +cli_auto_top(clicon_handle h, + cvec *cvv, + cvec *argv) +{ + int retval = -1; + cg_var *cv; + char *treename; + pt_head *ph; + cvec *cvv0 = NULL; + + cv = cvec_i(argv, 0); + treename = cv_string_get(cv); + if ((ph = cligen_ph_find(cli_cligen(h), treename)) == NULL){ + clicon_err(OE_PLUGIN, 0, "No such parsetree header: %s", treename); + goto done; + } + cligen_ph_workpoint_set(ph, NULL); + /* Store this as edit-mode */ + clicon_data_set(h, "cli-edit-mode", ""); + if ((cvv0 = clicon_data_cvec_get(h, "cli-edit-cvv")) != NULL) + clicon_data_del(h, "cli_edit-cvv"); + retval = 0; + done: + return retval; +} + +/*! CLI callback: Working point tree show + * @param[in] h CLICON handle + * @param[in] cvv Vector of variables from CLIgen command-line + * @param[in] argv Vector oif user-supplied keywords + * Format of argv: + * Name of generated cligen parse-tree, eg "datamodel" + * "running"|"candidate"|"startup" + * "text"|"xml"|"json"|"cli"|"netconf" (see format_enum) + * true|false: pretty-print or not + * true|false: pretty-print or not + * to print before cli syntax output + */ +int +cli_auto_show(clicon_handle h, + cvec *cvv, + cvec *argv) +{ + int retval = 1; + char *treename; + char *db; + char *api_path = NULL; + char *formatstr; + enum format_enum format; + enum genmodel_type gt; + pt_head *ph; + char *xpath = NULL; + cxobj *xp; + cxobj *xc = NULL; + cvec *nsc = NULL; + yang_stmt *yspec; + cxobj *xerr; + cxobj *xt = NULL; + cxobj **vec; + size_t veclen; + int i; + int isroot; + int pretty; + char *prefix = NULL; + int state; + cg_var *boolcv = NULL; + + if (cvec_len(argv) != 5 && cvec_len(argv) != 6){ + clicon_err(OE_PLUGIN, 0, "Usage: []."); + goto done; + } + /* First argv argument: treename */ + treename = cv_string_get(cvec_i(argv, 0)); + /* Second argv argument: Database */ + db = cv_string_get(cvec_i(argv, 1)); + /* Third format: output format */ + formatstr = cv_string_get(cvec_i(argv, 2)); + if ((int)(format = format_str2int(formatstr)) < 0){ + clicon_err(OE_PLUGIN, 0, "Not valid format: %s", formatstr); + goto done; + } + /* Fourth: pretty-print */ + if ((boolcv = cv_new(CGV_BOOL)) == NULL){ + clicon_err(OE_UNIX, errno, "cv_new"); + goto done; + } + if (cv_parse(cv_string_get(cvec_i(argv, 3)), boolcv) < 0){ + clicon_err(OE_UNIX, errno, "Parse boolean %s", cv_string_get(cvec_i(argv, 3))); + goto done; + } + pretty = cv_bool_get(boolcv); + /* Fifth: state */ + if (cv_parse(cv_string_get(cvec_i(argv, 4)), boolcv) < 0){ + clicon_err(OE_UNIX, errno, "Parse boolean %s", cv_string_get(cvec_i(argv, 4))); + goto done; + } + state = cv_bool_get(boolcv); + + /* Sixth: prefix */ + if (cvec_len(argv) == 6) { + prefix = cv_string_get(cvec_i(argv, 5)); + } + if ((yspec = clicon_dbspec_yang(h)) == NULL){ + clicon_err(OE_FATAL, 0, "No DB_SPEC"); + goto done; + } + if ((ph = cligen_ph_find(cli_cligen(h), treename)) == NULL){ /* XXX not used */ + clicon_err(OE_PLUGIN, 0, "No such parsetree header: %s", treename); + goto done; + } + /* Store this as edit-mode */ + if (clicon_data_get(h, "cli-edit-mode", &api_path) == 0 && strlen(api_path)) + ; + else + api_path = "/"; + if (api_path2xpath(api_path, yspec, &xpath, &nsc, NULL) < 0) + goto done; + isroot = (xpath == NULL) || strcmp(xpath,"/")==0; + if (state == 0){ /* Get configuration-only from database */ + if (clicon_rpc_get_config(h, NULL, db, xpath, nsc, &xt) < 0) + goto done; + } + else { /* Get configuration and state from database */ + if (strcmp(db, "running") != 0){ + clicon_err(OE_FATAL, 0, "Show state only for running database, not %s", db); + goto done; + } + if (clicon_rpc_get(h, xpath, nsc, CONTENT_ALL, -1, &xt) < 0) + goto done; + } + if ((xerr = xpath_first(xt, NULL, "/rpc-error")) != NULL){ + clixon_netconf_error(xerr, "Get configuration", NULL); + goto done; + } + if (xpath_vec(xt, nsc, "%s", &vec, &veclen, xpath) < 0) + goto done; + + for (i=0; i", + NETCONF_BASE_NAMESPACE); + if (pretty) + fprintf(stdout, "\n"); + if (isroot) + clicon_xml2file(stdout, xp, 2, pretty); + else + while ((xc = xml_child_each(xp, xc, CX_ELMNT)) != NULL) + clicon_xml2file(stdout, xc, 2, pretty); + fprintf(stdout, "]]>]]>\n"); + break; + } /* switch */ + } + retval = 0; + done: + if (boolcv) + cv_free(boolcv); + if (xt) + xml_free(xt); + if (nsc) + xml_nsctx_free(nsc); + if (vec) + free(vec); + if (xpath) + free(xpath); + return retval; +} + +/*! CLI callback: set auto db item + * @param[in] h Clicon handle + * @param[in] cvv Vector of cli string and instantiated variables + * @param[in] argv Vector. First element xml key format string, eg "/aaa/%s" + * Format of argv: + * Generated + */ +int +cli_auto_set(clicon_handle h, + cvec *cvv, + cvec *argv) +{ + int retval = -1; + cvec *cvv2 = NULL; + + cvv2 = cvec_append(clicon_data_cvec_get(h, "cli-edit-cvv"), cvv); + if (cli_dbxml(h, cvv2, argv, OP_REPLACE, NULL) < 0) + goto done; + retval = 0; + done: + if (cvv2) + cvec_free(cvv2); + return retval; +} + +/*! Merge datastore xml entry + * @param[in] h Clicon handle + * @param[in] cvv Vector of cli string and instantiated variables + * @param[in] argv Vector. First element xml key format string, eg "/aaa/%s" + */ +int +cli_auto_merge(clicon_handle h, + cvec *cvv, + cvec *argv) +{ + int retval = -1; + cvec *cvv2 = NULL; + + cvv2 = cvec_append(clicon_data_cvec_get(h, "cli-edit-cvv"), cvv); + if (cli_dbxml(h, cvv2, argv, OP_MERGE, NULL) < 0) + goto done; + retval = 0; + done: + if (cvv2) + cvec_free(cvv2); + return retval; +} + +/*! Create datastore xml entry + * @param[in] h Clicon handle + * @param[in] cvv Vector of cli string and instantiated variables + * @param[in] argv Vector. First element xml key format string, eg "/aaa/%s" + */ +int +cli_auto_create(clicon_handle h, + cvec *cvv, + cvec *argv) +{ + int retval = -1; + cvec *cvv2 = NULL; + + cvv2 = cvec_append(clicon_data_cvec_get(h, "cli-edit-cvv"), cvv); + if (cli_dbxml(h, cvv2, argv, OP_CREATE, NULL) < 0) + goto done; + retval = 0; + done: + if (cvv2) + cvec_free(cvv2); + return retval; +} + +/*! Delete datastore xml + * @param[in] h Clicon handle + * @param[in] cvv Vector of cli string and instantiated variables + * @param[in] argv Vector. First element xml key format string, eg "/aaa/%s" + */ +int +cli_auto_del(clicon_handle h, + cvec *cvv, + cvec *argv) +{ + int retval = -1; + cvec *cvv2 = NULL; + + cvv2 = cvec_append(clicon_data_cvec_get(h, "cli-edit-cvv"), cvv); + if (cli_dbxml(h, cvv2, argv, OP_REMOVE, NULL) < 0) + goto done; + retval = 0; + done: + if (cvv2) + cvec_free(cvv2); + return retval; +} diff --git a/apps/cli/cli_common.c b/apps/cli/cli_common.c index 27425311..d044bb7a 100644 --- a/apps/cli/cli_common.c +++ b/apps/cli/cli_common.c @@ -1316,6 +1316,6 @@ cli_help(clicon_handle h, cvec *vars, cvec *argv) cligen_handle ch = cli_cligen(h); parse_tree *pt; - pt = cligen_tree_active_get(ch); + pt = cligen_ph_active_get(ch); return cligen_help(ch, stdout, pt); } diff --git a/apps/cli/cli_main.c b/apps/cli/cli_main.c index 3897e953..0c56c84b 100644 --- a/apps/cli/cli_main.c +++ b/apps/cli/cli_main.c @@ -176,6 +176,7 @@ cli_terminate(clicon_handle h) cvec_free(nsctx); if ((x = clicon_conf_xml(h)) != NULL) xml_free(x); + clicon_data_cvec_del(h, "cli-edit-cvv");; xpath_optimize_exit(); cli_plugin_finish(h); cli_history_save(h); @@ -266,6 +267,7 @@ autocli_tree(clicon_handle h, int retval = -1; parse_tree *pt = NULL; /* cli parse tree */ yang_stmt *yspec; + pt_head *ph; if ((pt = pt_new()) == NULL){ clicon_err(OE_UNIX, errno, "pt_new"); @@ -276,7 +278,9 @@ autocli_tree(clicon_handle h, if (yang2cli(h, yspec, gt, printgen, state, show_tree, pt) < 0) goto done; /* Append cligen tree and name it */ - if (cligen_tree_add(cli_cligen(h), name, pt) < 0) + if ((ph = cligen_ph_add(cli_cligen(h), name)) == NULL) + goto done; + if (cligen_ph_parsetree_set(ph, pt) < 0) goto done; retval = 0; done: @@ -692,9 +696,8 @@ main(int argc, fprintf(stderr, "FATAL: No cli mode set (use -m or CLICON_CLI_MODE)\n"); goto done; } - if (cligen_tree_find(cli_cligen(h), cli_syntax_mode(h)) == NULL) + if (cligen_ph_find(cli_cligen(h), cli_syntax_mode(h)) == NULL) clicon_log(LOG_WARNING, "No such cli mode: %s (Specify cli mode with CLICON_CLI_MODE in config file or -m on command line", cli_syntax_mode(h)); - /* CLIgen tab mode, ie how s behave */ if ((tabmode = clicon_cli_tab_mode(h)) < 0){ fprintf(stderr, "FATAL: CLICON_CLI_TAB_MODE not set\n"); diff --git a/apps/cli/cli_plugin.c b/apps/cli/cli_plugin.c index 3584ca37..8b52f240 100644 --- a/apps/cli/cli_plugin.c +++ b/apps/cli/cli_plugin.c @@ -120,8 +120,16 @@ static int gen_parse_tree(clicon_handle h, cli_syntaxmode_t *m) { - cligen_tree_add(cli_cligen(h), m->csm_name, m->csm_pt); - return 0; + int retval = -1; + pt_head *ph; + + if ((ph = cligen_ph_add(cli_cligen(h), m->csm_name)) == NULL) + goto done; + if (cligen_ph_parsetree_set(ph, m->csm_pt) < 0) + goto done; + retval = 0; + done: + return retval; } /*! Append syntax @@ -568,13 +576,13 @@ clicon_parse(clicon_handle h, } if (smode){ modename0 = NULL; - if ((pt = cligen_tree_active_get(cli_cligen(h))) != NULL) + if ((pt = cligen_ph_active_get(cli_cligen(h))) != NULL) modename0 = pt_name_get(pt); - if (cligen_tree_active_set(cli_cligen(h), modename) < 0){ + if (cligen_ph_active_set(cli_cligen(h), modename) < 0){ fprintf(stderr, "No such parse-tree registered: %s\n", modename); goto done; } - if ((pt = cligen_tree_active_get(cli_cligen(h))) == NULL){ + if ((pt = cligen_ph_active_get(cli_cligen(h))) == NULL){ fprintf(stderr, "No such parse-tree registered: %s\n", modename); goto done; } @@ -589,7 +597,7 @@ clicon_parse(clicon_handle h, if (*result != CG_MATCH) pt_expand_cleanup(pt); /* XXX change to pt_expand_treeref_cleanup */ if (modename0){ - cligen_tree_active_set(cli_cligen(h), modename0); + cligen_ph_active_set(cli_cligen(h), modename0); modename0 = NULL; } switch (*result) { @@ -607,7 +615,8 @@ clicon_parse(clicon_handle h, } if ((r = clicon_eval(h, cmd, match_obj, cvv)) < 0) cli_handler_err(stdout); - pt_expand_cleanup(pt); /* XXX change to pt_expand_treeref_cleanup */ + pt_expand_cleanup(pt); + pt_expand_treeref_cleanup(pt); if (evalres) *evalres = r; break; @@ -655,8 +664,8 @@ clicon_cliread(clicon_handle h, if (clicon_quiet_mode(h)) cli_prompt_set(h, ""); else - cli_prompt_set(h, cli_prompt(pfmt ? pfmt : mode->csm_prompt)); - cligen_tree_active_set(cli_cligen(h), mode->csm_name); + cli_prompt_set(h, cli_prompt(h, pfmt ? pfmt : mode->csm_prompt)); + cligen_ph_active_set(cli_cligen(h), mode->csm_name); if (cliread(cli_cligen(h), stringp) < 0){ clicon_err(OE_FATAL, errno, "CLIgen"); @@ -724,14 +733,16 @@ cli_set_prompt(clicon_handle h, } /*! Format prompt + * @param[in] h Clicon handle * @param[out] prompt Prompt string to be written * @param[in] plen Length of prompt string - * @param[in] fmt Stdarg fmt string + * @param[in] str Stdarg fmt string */ static int -prompt_fmt(char *prompt, - size_t plen, - char *fmt, ...) +prompt_fmt(clicon_handle h, + char *prompt, + size_t plen, + char *fmt,...) { va_list ap; char *s = fmt; @@ -740,6 +751,7 @@ prompt_fmt(char *prompt, char *tmp; int ret = -1; cbuf *cb = NULL; + char *path = NULL; if ((cb = cbuf_new()) == NULL){ clicon_err(OE_XML, errno, "cbuf_new"); @@ -764,6 +776,13 @@ prompt_fmt(char *prompt, strcpy(tty, "notty"); cprintf(cb, "%s", tty); break; + case 'W': /* working edit path */ + if (clicon_data_get(h, "cli-edit-mode", &path) == 0 && + strlen(path)) + cprintf(cb, "%s", path); + else + cprintf(cb, "/"); + break; default: cprintf(cb, "%%"); cprintf(cb, "%c", *s); @@ -798,11 +817,12 @@ done: * @param[in] fmt Format string */ char * -cli_prompt(char *fmt) +cli_prompt(clicon_handle h, + char *fmt) { static char prompt[CLI_PROMPT_LEN]; - if (prompt_fmt(prompt, sizeof(prompt), fmt) < 0) + if (prompt_fmt(h, prompt, sizeof(prompt), fmt) < 0) return CLI_DEFAULT_PROMPT; return prompt; diff --git a/apps/cli/clixon_cli_api.h b/apps/cli/clixon_cli_api.h index 971c9a97..04e0b446 100644 --- a/apps/cli/clixon_cli_api.h +++ b/apps/cli/clixon_cli_api.h @@ -53,7 +53,7 @@ char *cli_syntax_mode(clicon_handle h); int cli_syntax_load(clicon_handle h); int cli_handler_err(FILE *fd); int cli_set_prompt(clicon_handle h, const char *mode, const char *prompt); -char *cli_prompt(char *fmt); +char *cli_prompt(clicon_handle h, char *fmt); int cli_ptpush(clicon_handle h, char *mode, char *string, char *op); int cli_ptpop(clicon_handle h, char *mode, char *op); @@ -71,6 +71,8 @@ int cli_notification_register(clicon_handle h, char *stream, enum format_enum fo int cli_dbxml(clicon_handle h, cvec *vars, cvec *argv, enum operation_type op, cvec *nsctx); +int cli_dbxml(clicon_handle h, cvec *vars, cvec *argv, enum operation_type op, cvec *nsctx); + int cli_set(clicon_handle h, cvec *vars, cvec *argv); int cli_merge(clicon_handle h, cvec *vars, cvec *argv); @@ -137,4 +139,15 @@ int cli_show_auto_state(clicon_handle h, cvec *cvv, cvec *argv); int cli_show_options(clicon_handle h, cvec *cvv, cvec *argv); +/* cli_auto.c: Autocli mode support */ + +int cli_auto_edit(clicon_handle h, cvec *cvv1, cvec *argv); +int cli_auto_up(clicon_handle h, cvec *cvv, cvec *argv); +int cli_auto_top(clicon_handle h, cvec *cvv, cvec *argv); +int cli_auto_show(clicon_handle h, cvec *cvv, cvec *argv); +int cli_auto_set(clicon_handle h, cvec *cvv, cvec *argv); +int cli_auto_merge(clicon_handle h, cvec *cvv, cvec *argv); +int cli_auto_create(clicon_handle h, cvec *cvv, cvec *argv); +int cli_auto_del(clicon_handle h, cvec *cvv, cvec *argv); + #endif /* _CLIXON_CLI_API_H_ */ diff --git a/example/main/clixon-example@2020-03-11.yang b/example/main/clixon-example@2020-03-11.yang index 71588734..a86fe7fc 100644 --- a/example/main/clixon-example@2020-03-11.yang +++ b/example/main/clixon-example@2020-03-11.yang @@ -44,6 +44,11 @@ module clixon-example { leaf value{ type string; } + leaf stat{ + description "Inline state data for example application"; + config false; + type int32; + } } } /* State data (not config) for the example application*/ diff --git a/lib/clixon/clixon_data.h b/lib/clixon/clixon_data.h index 79641950..802b0336 100644 --- a/lib/clixon/clixon_data.h +++ b/lib/clixon/clixon_data.h @@ -65,6 +65,10 @@ int clicon_data_get(clicon_handle h, const char *name, char **val); int clicon_data_set(clicon_handle h, const char *name, char *val); int clicon_data_del(clicon_handle h, const char *name); +cvec *clicon_data_cvec_get(clicon_handle h, const char *name); +int clicon_data_cvec_set(clicon_handle h, const char *name, cvec *cvv); +int clicon_data_cvec_del(clicon_handle h, const char *name); + yang_stmt * clicon_dbspec_yang(clicon_handle h); int clicon_dbspec_yang_set(clicon_handle h, yang_stmt *ys); diff --git a/lib/src/clixon_data.c b/lib/src/clixon_data.c index 9a3fc9b9..94aef622 100644 --- a/lib/src/clixon_data.c +++ b/lib/src/clixon_data.c @@ -134,6 +134,78 @@ clicon_data_del(clicon_handle h, return clicon_hash_del(cdat, (char*)name); } +/*! Get generic cligen varaibel vector (cvv) on the form = where is cvv + * + * @param[in] h Clicon handle + * @param[in] name Data name + * @retval cvv Data value as cvv + * @retval NULL Not found (or error) + * @code + * cvec *cvv = NULL; + * if (clicon_data_cvec_get(h, "mycvv", &cvv) < 0) + * err; + * @endcode + */ +cvec * +clicon_data_cvec_get(clicon_handle h, + const char *name) +{ + clicon_hash_t *cdat = clicon_data(h); + size_t len = 0; + void *p; + + if ((p = clicon_hash_value(cdat, (char*)name, &len)) != NULL) + return *(cvec **)p; + return NULL; +} + +/*! Set generic cligen varaibel vector (cvv) on the form = where is cvv + * @param[in] h Clicon handle + * @param[in] name Name + * @param[in] cvv CLIgen variable vector (cvv) (malloced) + */ +int +clicon_data_cvec_set(clicon_handle h, + const char *name, + cvec *cvv) +{ + clicon_hash_t *cdat = clicon_data(h); + cvec *cvv0; + + /* It is the pointer to cvec that should be copied by hash, + so we send a ptr to the ptr to indicate what to copy. + */ + if ((cvv0 = clicon_data_cvec_get(h, name)) != NULL){ + fprintf(stderr, "%s free %p\n", __FUNCTION__, cvv0); + cvec_free(cvv0); + } + fprintf(stderr, "%s set %p\n", __FUNCTION__, cvv); + if (clicon_hash_add(cdat, (char*)name, &cvv, sizeof(cvv)) == NULL) + return -1; + return 0; +} + +/*! Delete generic cligen varaibel vector (cvv) + * @param[in] h Clicon handle + * @param[in] name Name + */ +int +clicon_data_cvec_del(clicon_handle h, + const char *name) +{ + clicon_hash_t *cdat = clicon_data(h); + cvec *cvv0; + + /* It is the pointer to cvec that should be copied by hash, + so we send a ptr to the ptr to indicate what to copy. + */ + if ((cvv0 = clicon_data_cvec_get(h, name)) != NULL){ + fprintf(stderr, "%s free %p\n", __FUNCTION__, cvv0); + cvec_free(cvv0); + } + return clicon_hash_del(cdat, (char*)name); +} + /*! * @param[in] h Clicon handle * @retval yspec Yang spec diff --git a/test/test_cli_auto.sh b/test/test_cli_auto.sh new file mode 100755 index 00000000..7a01bb43 --- /dev/null +++ b/test/test_cli_auto.sh @@ -0,0 +1,285 @@ +#!/usr/bin/env bash +# Backend and cli basic functionality +# Start backend server +# Add an ethernet interface and an address +# Show configuration +# Validate without a mandatory type +# Set the mandatory type +# Commit + +# Magic line must be first in script (see README.md) +s="$_" ; . ./lib.sh || if [ "$s" = $0 ]; then exit 0; else return 0; fi + +APPNAME=example + +# include err() and new() functions and creates $dir + +cfg=$dir/conf_yang.xml +fspec=$dir/automode.cli +fin=$dir/in +fstate=$dir/state.xml + +# Use yang in example + +cat < $cfg + + $cfg + ietf-netconf:startup + /usr/local/share/clixon + $IETFRFC + clixon-example + /usr/local/lib/$APPNAME/backend + $APPNAME + /usr/local/lib/$APPNAME/cli + $dir + /usr/local/var/$APPNAME/$APPNAME.sock + /usr/local/var/$APPNAME/$APPNAME.pidfile + $dir + false + +EOF + +cat < $fspec +CLICON_MODE="example"; +CLICON_PROMPT="%U@%H %W> "; +CLICON_PLUGIN="example_cli"; + +# Autocli syntax tree operations +edit @datamodel, cli_auto_edit("datamodel", "candidate"); +up, cli_auto_up("datamodel", "candidate"); +top, cli_auto_top("datamodel", "candidate"); +set @datamodel, cli_auto_set(); +merge @datamodel, cli_auto_merge(); +create @datamodel, cli_auto_create(); +delete("Delete a configuration item") @datamodel, cli_auto_del(); + +validate("Validate changes"), cli_validate(); +commit("Commit the changes"), cli_commit(); +quit("Quit"), cli_quit(); +show("Show a particular state of the system"){ + configuration("Show configuration"), cli_auto_show("datamodel", "candidate", "xml", false, false);{ + xml("Show configuration as XML"), cli_auto_show("datamodel", "candidate", "xml", false, false); + cli("Show configuration as CLI commands"), cli_auto_show("datamodel", "candidate", "cli", false, false, "set "); + netconf("Show configuration as netconf edit-config operation"), cli_auto_show("datamodel", "candidate", "netconf", false, false); + text("Show configuration as text"), cli_auto_show("datamodel", "candidate", "text", false, false); + json("Show configuration as JSON"), cli_auto_show("datamodel", "candidate", "json", false, false); + } + state("Show configuration and state"), cli_auto_show("datamodel", "running", "xml", false, true); +} +EOF + +cat < $dir/startup_db + + + + a + 42 + +
+
+EOF + +# Add inline state +cat < $fstate + + + a + 99 + +
+EOF + +new "test params: -f $cfg" +if [ $BE -ne 0 ]; then + new "kill old backend" + sudo clixon_backend -z -f $cfg + if [ $? -ne 0 ]; then + err + fi + new "start backend -s startup -f $cfg -- -sS $fstate" + start_backend -s startup -f $cfg -- -sS $fstate + + new "waiting" + wait_backend +fi + +# First go down in structure and show config +new "show top tree" +expectpart "$(echo "show config" | $clixon_cli -f $cfg 2>&1)" 0 'a42
$' + +cat < $fin +up +show config +EOF +new "up show" +expectpart "$(cat $fin | $clixon_cli -f $cfg 2>&1)" 0 'a42
$' + +cat < $fin +edit table +show config +EOF +new "edit table; show" +expectpart "$(cat $fin | $clixon_cli -f $cfg 2>&1)" 0 "/clixon-example:table>" "a42$" --not-- '' + +cat < $fin +edit table parameter a +show config +EOF +new "edit table parameter a; show" +expectpart "$(cat $fin | $clixon_cli -f $cfg 2>&1)" 0 "/clixon-example:table/parameter=a/>" "a42" --not-- '
' "" + +cat < $fin +edit table +edit parameter a +show config +EOF +new "edit table; edit parameter a; show" +expectpart "$(cat $fin | $clixon_cli -f $cfg 2>&1)" 0 "a42" --not-- '
' "" + +cat < $fin +edit table parameter a value 42 +show config +EOF +new "edit table parameter a value 42; show" +expectpart "$(cat $fin | $clixon_cli -f $cfg 2>&1)" 0 --not-- '
' "" "a" "42" + +# edit -> top +cat < $fin +edit table parameter a value 42 +top +show config +EOF +new "edit table parameter a value 42; top; show" +expectpart "$(cat $fin | $clixon_cli -f $cfg 2>&1)" 0 '
a42
$' + +cat < $fin +edit table parameter a +top +show config +EOF +new "edit table parameter a; top; show" +expectpart "$(cat $fin | $clixon_cli -f $cfg 2>&1)" 0 'a42
$' + +# edit -> up + +cat < $fin +edit table +up +show config +EOF +new "edit table; up; show" +expectpart "$(cat $fin | $clixon_cli -f $cfg 2>&1)" 0 'a42
$' + +cat < $fin +edit table parameter a +up +show config +EOF +new "edit table parameter a; up; show" +expectpart "$(cat $fin | $clixon_cli -f $cfg 2>&1)" 0 "/clixon-example:table>" "a42$" --not-- '' + +cat < $fin +edit table parameter a +up +up +show config +EOF +new "edit table parameter a; up up; show" +expectpart "$(cat $fin | $clixon_cli -f $cfg 2>&1)" 0 '
a42
$' + +cat < $fin +edit table parameter a +up +edit parameter a +show config +EOF +new "edit table parameter a; up; edit parameter a; show" +expectpart "$(cat $fin | $clixon_cli -f $cfg 2>&1)" 0 "/clixon-example:table/parameter=a/>" "a42" --not-- '' "" + +# Create new field b, and remove it +cat < $fin +edit table parameter b +show config +EOF +new "edit table parameter b; show" +expectpart "$(cat $fin | $clixon_cli -f $cfg 2>&1)" 0 "/clixon-example:table/parameter=b/>" --not-- "a42" '
' "" + +cat < $fin +edit table parameter b +set value 71 +up +show config +EOF +new "set value 71" +expectpart "$(cat $fin | $clixon_cli -f $cfg 2>&1)" 0 "/clixon-example:table>" "a42b71" + +cat < $fin +edit table parameter b +delete value 17 +show config +EOF +new "delete value 71" +expectpart "$(cat $fin | $clixon_cli -f $cfg 2>&1)" 0 "b" --not-- "71" + +cat < $fin +edit table +delete parameter b +up +show config +EOF +new "delete parameter b" +expectpart "$(cat $fin | $clixon_cli -f $cfg 2>&1)" 0 '
a42
$' + +# Back to startup +# show state + +cat < $fin +edit table +show state +EOF +new "show state" +expectpart "$(cat $fin | $clixon_cli -f $cfg 2>&1)" 0 "a4299$" + +# Show other formats +cat < $fin +edit table +show config json +EOF +new "show config json" +expectpart "$(cat $fin | $clixon_cli -f $cfg 2>&1)" 0 '{"clixon-example:parameter":{"name":"a","value":"42"}}' + +cat < $fin +edit table +show config text +EOF +new "show config text" +expectpart "$(cat $fin | $clixon_cli -f $cfg 2>&1)" 0 "parameter {" "name a;" "value 42;" + +cat < $fin +edit table +show config cli +EOF +new "show config cli" +expectpart "$(cat $fin | $clixon_cli -f $cfg 2>&1)" 0 "set parameter a value 42$" + +cat < $fin +edit table +show config netconf +EOF +new "show config netconf" +expectpart "$(cat $fin | $clixon_cli -f $cfg 2>&1)" 0 'a42]]>]]>' + +endtest + +if [ $BE -ne 0 ]; then + new "Kill backend" + # Check if premature kill + pid=$(pgrep -u root -f clixon_backend) + if [ -z "$pid" ]; then + err "backend already dead" + fi + # kill backend + stop_backend -f $cfg +fi + +rm -rf $dir diff --git a/yang/clixon/clixon-config@2020-10-01.yang b/yang/clixon/clixon-config@2020-10-01.yang index f2dde0ea..4ef7a4ca 100644 --- a/yang/clixon/clixon-config@2020-10-01.yang +++ b/yang/clixon/clixon-config@2020-10-01.yang @@ -493,18 +493,23 @@ module clixon-config { 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. + 2: Same including state syntax in a tree called @datamodelstate and @datamodelshow See also CLICON_CLI_MODEL_TREENAME."; } leaf CLICON_CLI_MODEL_TREENAME { type string; default "datamodel"; description - "If set, CLI specs can reference the - model syntax using this reference. + "If CLICON_CLI_GENMOEL is set, CLI specs can reference the + model syntax using a model tree set by this option. + Three trees are generated with this name as a base, (assuming base is datamodel): + - @datamodel - a clispec for navigating in editing a configuration (set/merge/delete) + - @datamodelshow - a clispec for navigating in showing a configuration + - @datamodelstate - a clispec for navigating in showing a configuration WITH state Example: set @datamodel, cli_set(); - A second tree called eg @datamodelstate is created that - also contains state together with config."; + show @datamodelshow, cli_show_auto(); + show state @datamodelstate, cli_show_auto_state(); + "; } leaf CLICON_CLI_GENMODEL_COMPLETION { type int32;