* 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 inline state field to clixon-example.yang
* Added new clicon_data_cvec_*() API for generic cvec structs
This commit is contained in:
Olof hagsand 2020-10-14 10:54:10 +02:00
parent 8d901e1fde
commit 2d56c9674a
11 changed files with 972 additions and 12 deletions

View file

@ -29,6 +29,12 @@ Expected: 15 October 2020
### New features ### 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: * Added support for the following XPATH functions:
* `count`, `name`, `contains`, `not`, as defined in [xpath 1.0](https://www.w3.org/TR/xpath-10) * `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. * `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. * 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. * 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`. * Example: error-message: `Mandatory variable` -> `Mandatory variable of edit-config in module ietf-netconf`.
### Minor changes ### Minor changes
* Added inline state field to clixon-example.yang
* Added stricter check on schema-node identifier checking, such as for augments. * Added stricter check on schema-node identifier checking, such as for augments.
* These checks are now made at YANG loading time * These checks are now made at YANG loading time
* Added sanity check that a yang module name matches the filename * Added sanity check that a yang module name matches the filename

View file

@ -82,6 +82,7 @@ LIBSRC = cli_common.c
LIBSRC += cli_show.c LIBSRC += cli_show.c
LIBSRC += cli_handle.c LIBSRC += cli_handle.c
LIBSRC += cli_plugin.c LIBSRC += cli_plugin.c
LIBSRC += cli_auto.c
LIBOBJ = $(LIBSRC:.c=.o) LIBOBJ = $(LIBSRC:.c=.o)
# Name of lib # Name of lib

560
apps/cli/cli_auto.c Normal file
View file

@ -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 <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <signal.h>
#include <errno.h>
#include <stdarg.h>
#include <time.h>
#include <ctype.h>
#include <unistd.h>
#include <dirent.h>
#include <syslog.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/wait.h>
#include <sys/param.h>
#include <sys/mount.h>
#include <pwd.h>
#include <assert.h>
/* cligen */
#include <cligen/cligen.h>
/* clicon */
#include <clixon/clixon.h>
#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:
* <api_path_fmt> Generated API PATH (This is where we are in the tree)
* <treename> Name of generated cligen parse-tree, eg "datamodel"
* <dbname> "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:
* <treename> Name of generated cligen parse-tree, eg "datamodel"
* <dbname> "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<strlen(api_path_fmt0); i++)
if (api_path_fmt0[i] == '%')
j++;
cvv1 = cvec_new(0);
for (i=0; i<cvec_len(cvv0)-j; i++){
cv = cvec_i(cvv0, i);
cvec_append_var(cvv1, cv);
}
/* get api-path and xpath */
if (api_path_fmt2api_path(api_path_fmt1, cvv1, &api_path) < 0)
goto done;
/* Store this as edit-mode */
clicon_data_set(h, "cli-edit-mode", api_path);
clicon_data_cvec_set(h, "cli-edit-cvv", cvv1);
ok:
retval = 0;
done:
if (api_path)
free(api_path);
return retval;
}
/*! CLI callback: Working point tree reset to top level
* @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:
* <treename> Name of generated cligen parse-tree, eg "datamodel"
* <dbname> "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:
* <treename> Name of generated cligen parse-tree, eg "datamodel"
* <dbname> "running"|"candidate"|"startup"
* <format> "text"|"xml"|"json"|"cli"|"netconf" (see format_enum)
* <pretty> true|false: pretty-print or not
* <state> true|false: pretty-print or not
* <prefix> 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: <treename> <database> <format> <pretty> <state> [<prefix>].");
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<veclen; i++){
xp = vec[i];
/* Print configuration according to format */
switch (format){
case FORMAT_XML:
if (isroot)
clicon_xml2file(stdout, xp, 0, pretty);
else{
while ((xc = xml_child_each(xp, xc, CX_ELMNT)) != NULL)
clicon_xml2file(stdout, xc, 0, pretty);
}
fprintf(stdout, "\n");
break;
case FORMAT_JSON:
if (isroot)
xml2json_cb(stdout, xp, pretty, cligen_output);
else{
while ((xc = xml_child_each(xp, xc, CX_ELMNT)) != NULL)
xml2json_cb(stdout, xc, pretty, cligen_output);
}
fprintf(stdout, "\n");
break;
case FORMAT_TEXT:
if (isroot)
xml2txt_cb(stdout, xp, cligen_output); /* tree-formed text */
else
while ((xc = xml_child_each(xp, xc, CX_ELMNT)) != NULL)
xml2txt_cb(stdout, xc, cligen_output); /* tree-formed text */
break;
case FORMAT_CLI:
if ((gt = clicon_cli_genmodel_type(h)) == GT_ERR)
goto done;
if (isroot)
xml2cli_cb(stdout, xp, prefix, gt, cligen_output); /* cli syntax */
else
while ((xc = xml_child_each(xp, xc, CX_ELMNT)) != NULL)
xml2cli_cb(stdout, xc, prefix, gt, cligen_output); /* cli syntax */
break;
case FORMAT_NETCONF:
fprintf(stdout, "<rpc xmlns=\"%s\"><edit-config><target><candidate/></target><config>",
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, "</config></edit-config></rpc>]]>]]>\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:
* <api-path-fmt> 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;
}

View file

@ -1316,6 +1316,6 @@ cli_help(clicon_handle h, cvec *vars, cvec *argv)
cligen_handle ch = cli_cligen(h); cligen_handle ch = cli_cligen(h);
parse_tree *pt; parse_tree *pt;
pt = cligen_tree_active_get(ch); pt = cligen_ph_active_get(ch);
return cligen_help(ch, stdout, pt); return cligen_help(ch, stdout, pt);
} }

View file

@ -176,6 +176,7 @@ cli_terminate(clicon_handle h)
cvec_free(nsctx); cvec_free(nsctx);
if ((x = clicon_conf_xml(h)) != NULL) if ((x = clicon_conf_xml(h)) != NULL)
xml_free(x); xml_free(x);
clicon_data_cvec_del(h, "cli-edit-cvv");;
xpath_optimize_exit(); xpath_optimize_exit();
cli_plugin_finish(h); cli_plugin_finish(h);
cli_history_save(h); cli_history_save(h);
@ -266,6 +267,7 @@ autocli_tree(clicon_handle h,
int retval = -1; int retval = -1;
parse_tree *pt = NULL; /* cli parse tree */ parse_tree *pt = NULL; /* cli parse tree */
yang_stmt *yspec; yang_stmt *yspec;
pt_head *ph;
if ((pt = pt_new()) == NULL){ if ((pt = pt_new()) == NULL){
clicon_err(OE_UNIX, errno, "pt_new"); 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) if (yang2cli(h, yspec, gt, printgen, state, show_tree, pt) < 0)
goto done; goto done;
/* Append cligen tree and name it */ /* 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; goto done;
retval = 0; retval = 0;
done: done:
@ -692,9 +696,8 @@ main(int argc,
fprintf(stderr, "FATAL: No cli mode set (use -m or CLICON_CLI_MODE)\n"); fprintf(stderr, "FATAL: No cli mode set (use -m or CLICON_CLI_MODE)\n");
goto done; 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 <mode> on command line", cli_syntax_mode(h)); clicon_log(LOG_WARNING, "No such cli mode: %s (Specify cli mode with CLICON_CLI_MODE in config file or -m <mode> on command line", cli_syntax_mode(h));
/* CLIgen tab mode, ie how <tab>s behave */ /* CLIgen tab mode, ie how <tab>s behave */
if ((tabmode = clicon_cli_tab_mode(h)) < 0){ if ((tabmode = clicon_cli_tab_mode(h)) < 0){
fprintf(stderr, "FATAL: CLICON_CLI_TAB_MODE not set\n"); fprintf(stderr, "FATAL: CLICON_CLI_TAB_MODE not set\n");

View file

@ -120,8 +120,16 @@ static int
gen_parse_tree(clicon_handle h, gen_parse_tree(clicon_handle h,
cli_syntaxmode_t *m) cli_syntaxmode_t *m)
{ {
cligen_tree_add(cli_cligen(h), m->csm_name, m->csm_pt); int retval = -1;
return 0; 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 /*! Append syntax
@ -568,13 +576,13 @@ clicon_parse(clicon_handle h,
} }
if (smode){ if (smode){
modename0 = NULL; 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); 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); fprintf(stderr, "No such parse-tree registered: %s\n", modename);
goto done; 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); fprintf(stderr, "No such parse-tree registered: %s\n", modename);
goto done; goto done;
} }
@ -589,7 +597,7 @@ clicon_parse(clicon_handle h,
if (*result != CG_MATCH) if (*result != CG_MATCH)
pt_expand_cleanup(pt); /* XXX change to pt_expand_treeref_cleanup */ pt_expand_cleanup(pt); /* XXX change to pt_expand_treeref_cleanup */
if (modename0){ if (modename0){
cligen_tree_active_set(cli_cligen(h), modename0); cligen_ph_active_set(cli_cligen(h), modename0);
modename0 = NULL; modename0 = NULL;
} }
switch (*result) { switch (*result) {
@ -607,7 +615,8 @@ clicon_parse(clicon_handle h,
} }
if ((r = clicon_eval(h, cmd, match_obj, cvv)) < 0) if ((r = clicon_eval(h, cmd, match_obj, cvv)) < 0)
cli_handler_err(stdout); 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) if (evalres)
*evalres = r; *evalres = r;
break; break;
@ -656,7 +665,7 @@ clicon_cliread(clicon_handle h,
cli_prompt_set(h, ""); cli_prompt_set(h, "");
else else
cli_prompt_set(h, cli_prompt(h, pfmt ? pfmt : mode->csm_prompt)); cli_prompt_set(h, cli_prompt(h, pfmt ? pfmt : mode->csm_prompt));
cligen_tree_active_set(cli_cligen(h), mode->csm_name); cligen_ph_active_set(cli_cligen(h), mode->csm_name);
if (cliread(cli_cligen(h), stringp) < 0){ if (cliread(cli_cligen(h), stringp) < 0){
clicon_err(OE_FATAL, errno, "CLIgen"); clicon_err(OE_FATAL, errno, "CLIgen");

View file

@ -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_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_set(clicon_handle h, cvec *vars, cvec *argv);
int cli_merge(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); 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_ */ #endif /* _CLIXON_CLI_API_H_ */

View file

@ -44,6 +44,11 @@ module clixon-example {
leaf value{ leaf value{
type string; type string;
} }
leaf stat{
description "Inline state data for example application";
config false;
type int32;
}
} }
} }
/* State data (not config) for the example application*/ /* State data (not config) for the example application*/

View file

@ -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_set(clicon_handle h, const char *name, char *val);
int clicon_data_del(clicon_handle h, const char *name); 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); yang_stmt * clicon_dbspec_yang(clicon_handle h);
int clicon_dbspec_yang_set(clicon_handle h, yang_stmt *ys); int clicon_dbspec_yang_set(clicon_handle h, yang_stmt *ys);

View file

@ -134,6 +134,78 @@ clicon_data_del(clicon_handle h,
return clicon_hash_del(cdat, (char*)name); return clicon_hash_del(cdat, (char*)name);
} }
/*! Get generic cligen varaibel vector (cvv) on the form <name>=<val> where <val> 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 <name>=<val> where <val> 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 * @param[in] h Clicon handle
* @retval yspec Yang spec * @retval yspec Yang spec

285
test/test_cli_auto.sh Executable file
View file

@ -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 <<EOF > $cfg
<clixon-config xmlns="http://clicon.org/config">
<CLICON_CONFIGFILE>$cfg</CLICON_CONFIGFILE>
<CLICON_FEATURE>ietf-netconf:startup</CLICON_FEATURE>
<CLICON_YANG_DIR>/usr/local/share/clixon</CLICON_YANG_DIR>
<CLICON_YANG_DIR>$IETFRFC</CLICON_YANG_DIR>
<CLICON_YANG_MODULE_MAIN>clixon-example</CLICON_YANG_MODULE_MAIN>
<CLICON_BACKEND_DIR>/usr/local/lib/$APPNAME/backend</CLICON_BACKEND_DIR>
<CLICON_CLI_MODE>$APPNAME</CLICON_CLI_MODE>
<CLICON_CLI_DIR>/usr/local/lib/$APPNAME/cli</CLICON_CLI_DIR>
<CLICON_CLISPEC_DIR>$dir</CLICON_CLISPEC_DIR>
<CLICON_SOCK>/usr/local/var/$APPNAME/$APPNAME.sock</CLICON_SOCK>
<CLICON_BACKEND_PIDFILE>/usr/local/var/$APPNAME/$APPNAME.pidfile</CLICON_BACKEND_PIDFILE>
<CLICON_XMLDB_DIR>$dir</CLICON_XMLDB_DIR>
<CLICON_XMLDB_PRETTY>false</CLICON_XMLDB_PRETTY>
</clixon-config>
EOF
cat <<EOF > $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 <<EOF > $dir/startup_db
<config>
<table xmlns="urn:example:clixon">
<parameter>
<name>a</name>
<value>42</value>
</parameter>
</table>
</config>
EOF
# Add inline state
cat <<EOF > $fstate
<table xmlns="urn:example:clixon">
<parameter>
<name>a</name>
<stat>99</stat>
</parameter>
</table>
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 '<table xmlns="urn:example:clixon"><parameter><name>a</name><value>42</value></parameter></table>$'
cat <<EOF > $fin
up
show config
EOF
new "up show"
expectpart "$(cat $fin | $clixon_cli -f $cfg 2>&1)" 0 '<table xmlns="urn:example:clixon"><parameter><name>a</name><value>42</value></parameter></table>$'
cat <<EOF > $fin
edit table
show config
EOF
new "edit table; show"
expectpart "$(cat $fin | $clixon_cli -f $cfg 2>&1)" 0 "/clixon-example:table>" "<parameter><name>a</name><value>42</value></parameter>$" --not-- '<table xmlns="urn:example:clixon">'
cat <<EOF > $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/>" "<name>a</name><value>42</value>" --not-- '<table xmlns="urn:example:clixon">' "<parameter>"
cat <<EOF > $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 "<name>a</name><value>42</value>" --not-- '<table xmlns="urn:example:clixon">' "<parameter>"
cat <<EOF > $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-- '<table xmlns="urn:example:clixon">' "<parameter>" "<name>a</name>" "<value>42</value>"
# edit -> top
cat <<EOF > $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 '<table xmlns="urn:example:clixon"><parameter><name>a</name><value>42</value></parameter></table>$'
cat <<EOF > $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 '<table xmlns="urn:example:clixon"><parameter><name>a</name><value>42</value></parameter></table>$'
# edit -> up
cat <<EOF > $fin
edit table
up
show config
EOF
new "edit table; up; show"
expectpart "$(cat $fin | $clixon_cli -f $cfg 2>&1)" 0 '<table xmlns="urn:example:clixon"><parameter><name>a</name><value>42</value></parameter></table>$'
cat <<EOF > $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>" "<parameter><name>a</name><value>42</value></parameter>$" --not-- '<table xmlns="urn:example:clixon">'
cat <<EOF > $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 '<table xmlns="urn:example:clixon"><parameter><name>a</name><value>42</value></parameter></table>$'
cat <<EOF > $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/>" "<name>a</name><value>42</value>" --not-- '<table xmlns="urn:example:clixon">' "<parameter>"
# Create new field b, and remove it
cat <<EOF > $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-- "<name>a</name><value>42</value>" '<table xmlns="urn:example:clixon">' "<parameter>"
cat <<EOF > $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>" "<parameter><name>a</name><value>42</value></parameter><parameter><name>b</name><value>71</value></parameter>"
cat <<EOF > $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 "<name>b</name>" --not-- "<value>71</value>"
cat <<EOF > $fin
edit table
delete parameter b
up
show config
EOF
new "delete parameter b"
expectpart "$(cat $fin | $clixon_cli -f $cfg 2>&1)" 0 '<table xmlns="urn:example:clixon"><parameter><name>a</name><value>42</value></parameter></table>$'
# Back to startup
# show state
cat <<EOF > $fin
edit table
show state
EOF
new "show state"
expectpart "$(cat $fin | $clixon_cli -f $cfg 2>&1)" 0 "<parameter><name>a</name><value>42</value><stat>99</stat></parameter>$"
# Show other formats
cat <<EOF > $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 <<EOF > $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 <<EOF > $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 <<EOF > $fin
edit table
show config netconf
EOF
new "show config netconf"
expectpart "$(cat $fin | $clixon_cli -f $cfg 2>&1)" 0 '<rpc xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"><edit-config><target><candidate/></target><config><parameter><name>a</name><value>42</value></parameter></config></edit-config></rpc>]]>]]>'
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