* [Strict auto completion for CLI argument expansion #163](https://github.com/clicon/clixon/issues/163)

* Fixed a bug with the extension API: different unknown statements were treated as same
This commit is contained in:
Olof hagsand 2022-03-01 09:24:29 +01:00
parent cf2de375f7
commit 0a593cd513
8 changed files with 437 additions and 15 deletions

View file

@ -62,6 +62,7 @@ Users may have to change how they access the system
### Minor features
* [Strict auto completion for CLI argument expansion #163](https://github.com/clicon/clixon/issues/163)
* [Convert int64, uint64 and decimal64 to string in xml to json #310](https://github.com/clicon/clixon/pull/310)
* Backend ignore of SIGPIPE. This occurs if client quits unexpectedly over the UNIX socket.
* This is a timing issue but occurs more frequently in large RESTCONF messgaes.

View file

@ -128,6 +128,7 @@ cli_expand_var_generate(clicon_handle h,
char *cvtypestr,
int options,
uint8_t fraction_digits,
int pre,
cbuf *cb)
{
int retval = -1;
@ -142,7 +143,9 @@ cli_expand_var_generate(clicon_handle h,
}
if (yang2api_path_fmt(ys, 1, &api_path_fmt) < 0)
goto done;
cprintf(cb, "|<%s:%s", yang_argument_get(ys), cvtypestr);
if (pre)
cprintf(cb, "|");
cprintf(cb, "<%s:%s", yang_argument_get(ys), cvtypestr);
if (options & YANG_OPTIONS_FRACTION_DIGITS)
cprintf(cb, " fraction-digits:%u", fraction_digits);
cprintf(cb, " %s(\"candidate\",\"%s\")>",
@ -600,6 +603,8 @@ yang2cli_var_leafref(clicon_handle h,
int completionp;
char *cvtypestr;
int ret;
int flag;
int regular_value = 1; /* if strict-expand==0 then regular-value is false */
/* Give up: use yreferred
* XXX: inline of else clause below
@ -613,21 +618,25 @@ yang2cli_var_leafref(clicon_handle h,
strcmp(type, "identityref") != 0 &&
strcmp(type, "bits") != 0;
}
if (completionp)
cprintf(cb, "(");
if (yang2cli_var_sub(h, ys, yrestype, helptext, cvtype,
options, cvv, patterns, fraction_digits, cb) < 0)
if (yang_extension_value(ys, "strict-expand", CLIXON_AUTOCLI_NS, &flag, NULL) < 0)
goto done;
regular_value = !flag;
if (completionp && regular_value)
cprintf(cb, "(");
if (regular_value)
if (yang2cli_var_sub(h, ys, yrestype, helptext, cvtype,
options, cvv, patterns, fraction_digits, cb) < 0)
goto done;
if (completionp){
if ((ret = cli_expand_var_generate(h, ys, cvtypestr,
options, fraction_digits,
options, fraction_digits, regular_value,
cb)) < 0)
goto done;
if (ret == 0)
yang2cli_helptext(cb, helptext);
cprintf(cb, ")");
}
if (completionp && regular_value)
cprintf(cb, ")");
retval = 0;
done:
return retval;
@ -694,7 +703,7 @@ yang2cli_var(clicon_handle h,
goto done;
if (completionp){
if ((result = cli_expand_var_generate(h, ys, cvtypestr,
options, fraction_digits,cb)) < 0)
options, fraction_digits, 1, cb)) < 0)
goto done;
if (result == 0)
yang2cli_helptext(cb, helptext);
@ -738,7 +747,6 @@ yang2cli_var(clicon_handle h,
cvv, patterns, fraction_digits, cb) < 0)
goto done;
}
ok:
retval = 0;
done:
@ -922,7 +930,6 @@ yang2cli_container(clicon_handle h,
return retval;
}
/*! Generate CLI code for Yang list statement
* @param[in] h Clixon handle
* @param[in] ys Yang statement

View file

@ -3770,6 +3770,8 @@ yang_extension_value(yang_stmt *ys,
cbuf *cb = NULL;
int ret;
if (exist)
*exist = 0;
if ((cb = cbuf_new()) == NULL){
clicon_err(OE_UNIX, errno, "cbuf_new");
goto done;

View file

@ -68,10 +68,10 @@ CLIXON_VERSION=@CLIXON_VERSION@
# see also DATASTORE_TOP_SYMBOL
DATASTORE_TOP="config"
# clixon yang revisions occuring in tests
CLIXON_AUTOCLI_REV="2021-12-05"
# clixon yang revisions occuring in tests (see eg yang/clixon/Makefile.in)
CLIXON_AUTOCLI_REV="2022-02-11"
CLIXON_LIB_REV="2021-12-05"
CLIXON_CONFIG_REV="2021-11-11"
CLIXON_CONFIG_REV="2022-02-11"
CLIXON_RESTCONF_REV="2021-05-20"
CLIXON_EXAMPLE_REV="2020-12-01"

View file

@ -0,0 +1,160 @@
#!/usr/bin/env bash
# autocli extension strict expansion
# See https://github.com/clicon/clixon/issues/163
# test is: add a couple of expansion alternatives, ensure cli cannot select any oother option
# 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
cfg=$dir/conf_yang.xml
fyang=$dir/example.yang
clidir=$dir/cli
if [ -d $clidir ]; then
rm -rf $clidir/*
else
mkdir $clidir
fi
# Use yang in example
# Generate autocli for these modules
AUTOCLI=$(autocli_config ${APPNAME}\* kw-nokey false)
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>${YANG_INSTALLDIR}</CLICON_YANG_DIR>
<CLICON_YANG_DIR>$dir</CLICON_YANG_DIR>
<CLICON_YANG_MAIN_DIR>$dir</CLICON_YANG_MAIN_DIR>
<CLICON_BACKEND_DIR>/usr/local/lib/$APPNAME/backend</CLICON_BACKEND_DIR>
<CLICON_CLISPEC_DIR>$clidir</CLICON_CLISPEC_DIR>
<CLICON_CLI_DIR>/usr/local/lib/$APPNAME/cli</CLICON_CLI_DIR>
<CLICON_CLI_MODE>$APPNAME</CLICON_CLI_MODE>
<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_MODULE_LIBRARY_RFC7895>false</CLICON_MODULE_LIBRARY_RFC7895>
${AUTOCLI}
</clixon-config>
EOF
cat <<EOF > $clidir/ex.cli
CLICON_MODE="example";
CLICON_PROMPT="%U@%H %W> ";
# Autocli syntax tree operations
edit @datamodel, cli_auto_edit("datamodel");
up, cli_auto_up("datamodel");
top, cli_auto_top("datamodel");
set @datamodel, cli_auto_set();
merge @datamodel, cli_auto_merge();
create @datamodel, cli_auto_create();
delete("Delete a configuration item") {
@datamodel, cli_auto_del();
all("Delete whole candidate configuration"), delete_all("candidate");
}
show("Show a particular state of the system")
configuration("Show configuration"), cli_auto_show("datamodel", "candidate", "xml", false, false);
EOF
# Yang specs must be here first for backend. But then the specs are changed but just for CLI
# Annotate original Yang spec example directly
# First annotate /table/parameter
# Had a problem with unknown in grouping -> test uses uses/grouping
cat <<EOF > $fyang
module example {
namespace "urn:example:clixon";
prefix ex;
import clixon-autocli{
prefix autocli;
}
container table{
list parameter{
key name;
leaf name{
autocli:strict-expand;
type string;
}
leaf value{
type string;
}
leaf value2{
autocli:strict-expand;
type string;
}
leaf-list value3{
autocli:strict-expand;
type string;
}
}
}
}
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 init -f $cfg"
start_backend -s init -f $cfg
fi
new "wait backend"
wait_backend
# The list
new "Add three list options"
expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO<rpc $DEFAULTNS><edit-config><default-operation>merge</default-operation><target><candidate/></target><config><table xmlns=\"urn:example:clixon\"><parameter><name>opta</name><value>42</value></parameter><parameter><name>optb</name><value>43</value></parameter><parameter><name>optc</name><value>44</value></parameter></table></config></edit-config></rpc>]]>]]>" "^<rpc-reply $DEFAULTNS><ok/></rpc-reply>]]>]]>$"
new "netconf commit"
expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO<rpc $DEFAULTNS><commit/></rpc>]]>]]>" "^<rpc-reply $DEFAULTNS><ok/></rpc-reply>]]>]]>$"
new "show config"
expectpart "$($clixon_cli -1 -f $cfg show conf)" 0 "<table xmlns=\"urn:example:clixon\"><parameter><name>opta</name><value>42</value></parameter><parameter><name>optb</name><value>43</value></parameter><parameter><name>optc</name><value>44</value></parameter></table>"
new "Check query"
expectpart "$(echo "set table parameter ?" | $clixon_cli -f $cfg 2>&1)" 0 opta optb optc --not-- '<name>'
new "Check completion"
expectpart "$(echo "set table parameter " | $clixon_cli -f $cfg 2>&1)" 0 opta optb optc --not-- '<name>'
# Leafs
new "Add leafs"
expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO<rpc $DEFAULTNS><edit-config><default-operation>merge</default-operation><target><candidate/></target><config><table xmlns=\"urn:example:clixon\"><parameter><name>opta</name><value>42</value><value2>43</value2><value3>44</value3></parameter></table></config></edit-config></rpc>]]>]]>" "^<rpc-reply $DEFAULTNS><ok/></rpc-reply>]]>]]>$"
new "netconf commit"
expecteof "$clixon_netconf -qf $cfg" 0 "$DEFAULTHELLO<rpc $DEFAULTNS><commit/></rpc>]]>]]>" "^<rpc-reply $DEFAULTNS><ok/></rpc-reply>]]>]]>$"
new "show config leafs"
expectpart "$($clixon_cli -1 -f $cfg show conf)" 0 "<table xmlns=\"urn:example:clixon\"><parameter><name>opta</name><value>42</value><value2>43</value2><value3>44</value3></parameter><parameter><name>optb</name><value>43</value></parameter><parameter><name>optc</name><value>44</value></parameter></table>"
new "Check query value"
expectpart "$(echo "set table parameter opta value ?" | $clixon_cli -f $cfg 2>&1)" 0 42 '<value>'
new "Check query value2"
expectpart "$(echo "set table parameter opta value2 ?" | $clixon_cli -f $cfg 2>&1)" 0 43 --not-- '<value>'
new "Check query value3"
expectpart "$(echo "set table parameter opta value3 ?" | $clixon_cli -f $cfg 2>&1)" 0 44 --not-- '<value>'
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
new "endtest"
endtest

View file

@ -17,6 +17,9 @@ fyang=$dir/type.yang
fyang2=$dir/example2.yang
fyang3=$dir/example3.yang
# Generate autocli for these modules
AUTOCLI=$(autocli_config ${APPNAME}\* kw-nokey false)
# transitive type, exists in fyang3, referenced from fyang2, but not declared in fyang
cat <<EOF > $fyang3
module example3{
@ -228,6 +231,7 @@ function testrun(){
<CLICON_XMLDB_DIR>/usr/local/var/$APPNAME</CLICON_XMLDB_DIR>
<CLICON_DATASTORE_CACHE>$dbcache</CLICON_DATASTORE_CACHE>
<CLICON_XMLDB_FORMAT>$format</CLICON_XMLDB_FORMAT>
${AUTOCLI}
</clixon-config>
EOF

View file

@ -41,12 +41,13 @@ datarootdir = @datarootdir@
# Also mandatory standard YANGs (see ../mandatory)
YANG_INSTALLDIR = @YANG_INSTALLDIR@
# Note: mirror these to test/config.sh.in
YANGSPECS = clixon-config@2022-02-11.yang # 5.6
YANGSPECS += clixon-lib@2021-12-05.yang # 5.5
YANGSPECS += clixon-rfc5277@2008-07-01.yang
YANGSPECS += clixon-xml-changelog@2019-03-21.yang
YANGSPECS += clixon-restconf@2021-05-20.yang # 5.2
YANGSPECS += clixon-autocli@2021-12-05.yang # 5.5
YANGSPECS += clixon-autocli@2022-02-11.yang # 5.6
all:

View file

@ -0,0 +1,247 @@
module clixon-autocli{
yang-version 1.1;
namespace "http://clicon.org/autocli";
prefix autocli;
organization
"Clicon / Clixon";
contact
"Olof Hagsand <olof@hagsand.se>";
description
"Clixon CLIgen specification declarations, including autocli.
Design inspired by ietf-netconf-acm.yang
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2020-2021 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 *****";
revision 2022-02-11 {
description
"Initial version
Released in Clixon 5.6";
}
revision 2021-12-05 {
description
"Initial version
Released in Clixon 5.5";
}
extension hide {
description
"Modify the autocli by hiding the command associated with a YANG node and its
sub-commands.
The command is active but not shown by ? or TAB. In other words, it hides the
auto-completion of commands";
}
extension hide-show {
description
"Modify the autocli by hiding the command associated with a YANG node and its
sub-commands in CLI show commands.";
}
extension strict-expand {
description
"Modify the autocli by only showing exactly the expanded values of a variable.
It should not be possible to add a new value that is not in the expanded list.";
}
typedef autocli-op {
description
"Autocli rule-type operation, each rule use different fields as
described in the individual enums below.";
type enumeration {
enum enable {
description
"Include a complete subtree to rendering of autocli.
Example:
<module-default>false</module-default>
<rule>
<name>wifi</name>
<operation>enable</operation>
<module-name>openconfig-wifi</module-name>
</rule>
Only on module-level and if module-default is false,
Rule fields used: module-name";
}
enum compress {
description
"Skip a keyword from a command.
Keep the command, only make it shorter by omitting a part.
Example: compress containers if single list child
<rule>
<name>container compress</name>
<operation>compress</operation>
<yang-keyword>container</yang-keyword>
<yang-keyword-child>list</yang-keyword-child>
</rule>
Rule fields used:
module-name, yang-keyword, schema-nodeid, yang-keyword-child, extension";
}
enum edit-mode {
description
"Autocli CLI edit modes for YANG symbols.
For example,
edit interface eth0<CR>
enters a new mode with local context.";
}
}
}
typedef list-keyword-type {
description
"Autocli CLI keyword behaviour in YANG lists.
With 'keyword' is meant CLIgen 'constants' rather than 'variables'.
Assume a YANG LIST: list a{ key x; leaf x; leaf y;} and how to generate
the autocli";
type enumeration {
enum kw-none{
description "No extra keywords, only variables: a <x> <y>";
}
enum kw-nokey{
description "Keywords on non-key variables: a <x> y <y>";
}
enum kw-all{
description "Keywords on all variables: a x <x> y <y>";
}
}
}
typedef yang-keywords {
type bits {
bit list;
bit listall{ /* NYI */
description
"Variant of list encompassing all list entries, not just an instance";
}
bit container;
bit leaf; /* Also leaf-list (NYI) */
}
}
grouping clixon-autocli{
/* options */
leaf module-default {
description
"Include YANG modules for generation of autocli.
If true, all modules with a top-level datanode are generated, ie
they get a top-level entry in the @basemodel tree.
If false, you need to explicitly enable modules for autocli generation
using 'enable' rules";
type boolean;
default true;
}
leaf list-keyword-default {
description
"Autocli CLI keyword behaviour in YANG lists.";
type list-keyword-type;
default kw-nokey;
}
leaf treeref-state-default {
description
"If 'true', generate CLI from YANG state/non-config statements as well, not only config data.
Many specs have very large state parts, for example openconfig has ca 10 times
larger state than config parts, see for example openconfig-isis.yang.";
type boolean;
default false;
}
leaf edit-mode-default {
description
"Open automatic edit-modes for some YANG keywords and do not allow others.
A CLI edit mode opens a carriage-return option and changes the context to be
in that local context.
For example:
cli> interfaces interface e0<cr>
eth0>
Default is to generate edit-modes for all containers and lists.";
type yang-keywords;
default "list container";
}
leaf completion-default {
description
"Generate code for CLI completion of existing db symbols.
That is, check existing configure database for completion options.
This is normally always enabled.";
type boolean;
default true;
}
/* rules */
list rule {
description
"Represents a modification rule of a clixon clispec.";
key name;
leaf name {
description
"Arbitrary name assigned for the rule, must be unique";
type string;
}
leaf description {
description
"Rule description";
type string;
}
leaf operation {
description "Rule operation";
type autocli-op;
}
leaf module-name {
description
"Name of the module associated with this rule.
Wildchars '*' and '?' can be used (glob pattern).
Revision and yang suffix are omitted
Example: 'openconfig-*'";
type string;
}
leaf yang-keyword {
description
"If present identifes a YANG keyword which the rule applies to
Example: 'container'
";
type string;
}
leaf schema-nodeid {
description
"path in the form of /<id>/<id> or just a single <id> identifying a YANG
schema-node identifier as defined in RFC 7950 Sec 6.5
Example: 'config', '/interfaces/interface'";
type string;
}
leaf yang-keyword-child {
description
"The YANG statement has a single child, and the yang type of the child is the
value of this option
A (maybe too) specific property to cover openconfig compressions
as defined here:
https://github.com/openconfig/ygot/blob/master/docs/design.md#openconfig-path-compression";
type string;
}
leaf extension {
/* Consider making this a container with name/module/value instead */
description
"The extension is set either in the node itself, or in this module
Extension prefix must be set
Example: oc-ext:openconfig-version";
type string;
}
}
}
}