* Renamed clixon-clispec.yang to clixon-autocli.yang
* First version of clixon-autocli.yang semantics * Default rules for module exclusion, list-keywords, completion, treeref-state * Specialized rules for compress and exclusion of modules * See [autocli documentation](https://clixon-docs.readthedocs.io/en/latest/cli.html#autocli) * Obsoleted and moved autocli config options from clixon-config.yang to clixon-autocli.yang as follows: * `CLICON_CLI_GENMODEL`, use: `autocli/module-default=false` instead * Removed `clicon_cli_genmodel()` * `CLICON_CLI_GENMODEL_TYPE`, use `autocli/list-keyword-default` and compress rules instead) * Removed `clicon_cli_genmodel_type()` * `CLICON_CLI_GENMODEL_COMPLETION`, use `autocli/completion-default` instead * Removed `clicon_cli_genmodel_completion()` * `CLICON_CLI_AUTOCLI_EXCLUDE`, use `autocli/rule/operation=exclude` instead * `CLICON_CLI_MODEL_TREENAME`, use constant `AUTOCLI_TREENAME` instead * Removed `clicon_cli_model_treename()` * New YANG functions: yang_single_child_type, yang_find_namespace_by_prefix, yang_str2key * Changed return values of yang_find_prefix_by_namespace * Merged `cli_cli2xml()` into `cli2xml()`
This commit is contained in:
parent
dec05e2cae
commit
081e6871b3
45 changed files with 1804 additions and 900 deletions
462
apps/cli/cli_autocli.c
Normal file
462
apps/cli/cli_autocli.c
Normal file
|
|
@ -0,0 +1,462 @@
|
|||
/*
|
||||
*
|
||||
***** 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 *****
|
||||
*
|
||||
* C-code corresponding to clixon-autocli.yang
|
||||
*/
|
||||
#ifdef HAVE_CONFIG_H
|
||||
#include "clixon_config.h" /* generated by config & autoconf */
|
||||
#endif
|
||||
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <syslog.h>
|
||||
#include <signal.h>
|
||||
#include <fnmatch.h>
|
||||
#include <sys/param.h>
|
||||
|
||||
/* cligen */
|
||||
#include <cligen/cligen.h>
|
||||
|
||||
/* libclixon */
|
||||
#include <clixon/clixon.h>
|
||||
|
||||
#include "clixon_cli_api.h"
|
||||
#include "cli_autocli.h"
|
||||
|
||||
/* Mapping from YANG autocli-op to C enum */
|
||||
static const map_str2int autocli_op_map[] = {
|
||||
{"enable", AUTOCLI_OP_ENABLE},
|
||||
{"compress", AUTOCLI_OP_COMPRESS},
|
||||
{NULL, -1}
|
||||
};
|
||||
|
||||
/* Mapping from YANG list-keyword-type to C enum */
|
||||
static const map_str2int list_kw_map[] = {
|
||||
{"kw-none", AUTOCLI_LISTKW_NONE},
|
||||
{"kw-nokey", AUTOCLI_LISTKW_NOKEY},
|
||||
{"kw-all", AUTOCLI_LISTKW_ALL},
|
||||
{NULL, -1}
|
||||
};
|
||||
|
||||
|
||||
static int
|
||||
autocli_str2op(char *str)
|
||||
{
|
||||
return clicon_str2int(autocli_op_map, str);
|
||||
}
|
||||
|
||||
#ifdef NOTUSED
|
||||
static const char *
|
||||
autocli_op2str(int op)
|
||||
{
|
||||
return clicon_int2str(autocli_op_map, op);
|
||||
}
|
||||
#endif
|
||||
|
||||
static int
|
||||
autocli_listkw_str2int(char *str)
|
||||
{
|
||||
return clicon_str2int(list_kw_map, str);
|
||||
}
|
||||
|
||||
#ifdef NOTUSED
|
||||
static const char *
|
||||
autocli_listkw_int2str(int listkw)
|
||||
{
|
||||
return clicon_int2str(list_kw_map, listkw);
|
||||
}
|
||||
#endif
|
||||
|
||||
/*! Filter module name according to cli_autocli.yang setting
|
||||
*
|
||||
* Traverse and find module/include/exclude rules
|
||||
* @param[in] h Clixon handle
|
||||
* @param[in] modname Name of YANG module, or NULL for ANY module (eg default)
|
||||
* @param[out] enablep Include this module in autocli
|
||||
* @retval -1 Error
|
||||
* @retval 0 OK, and enablep set
|
||||
*/
|
||||
int
|
||||
autocli_module(clicon_handle h,
|
||||
char *modname,
|
||||
int *enablep)
|
||||
{
|
||||
int retval = -1;
|
||||
cxobj *xrule;
|
||||
cxobj *xmod;
|
||||
char *str;
|
||||
char *element;
|
||||
int enable = 0;
|
||||
cxobj *xautocli;
|
||||
char *body;
|
||||
|
||||
if (enablep == NULL){
|
||||
clicon_err(OE_YANG, EINVAL, "Argument is NULL");
|
||||
goto done;
|
||||
}
|
||||
enable = 0;
|
||||
if ((xautocli = clicon_conf_autocli(h)) == NULL)
|
||||
goto ok;
|
||||
/* Default rule */
|
||||
if ((str = xml_find_body(xautocli, "module-default")) == NULL){
|
||||
clicon_err(OE_XML, EINVAL, "No module-default rule");
|
||||
goto done;
|
||||
}
|
||||
enable = strcmp(str, "true") == 0;
|
||||
if (!enable){
|
||||
xrule = NULL;
|
||||
while ((xrule = xml_child_each(xautocli, xrule, CX_ELMNT)) != NULL) {
|
||||
if (strcmp(xml_name(xrule), "rule") != 0)
|
||||
continue;
|
||||
if ((str = xml_find_body(xrule, "operation")) == NULL)
|
||||
continue;
|
||||
/* Peek in element of rule to skip non-compress operations */
|
||||
if (autocli_str2op(str) != AUTOCLI_OP_ENABLE)
|
||||
continue;
|
||||
/* At this point this rule is a compress rule
|
||||
* enable rules logic is:
|
||||
* - If match, break, done
|
||||
*/
|
||||
xmod = NULL;
|
||||
while ((xmod = xml_child_each(xrule, xmod, CX_ELMNT)) != NULL) {
|
||||
if ((element = xml_name(xmod)) == NULL)
|
||||
continue;
|
||||
if (strcmp(element, "module-name") == 0){
|
||||
if (modname == NULL)
|
||||
break; /* match */
|
||||
if ((body = xml_body(xmod)) == NULL)
|
||||
continue; /* invalid rule? */
|
||||
if (fnmatch(body, modname, 0) == 0)
|
||||
break; /* match */
|
||||
}
|
||||
}
|
||||
if (xmod != NULL){ /* break: found match */
|
||||
enable = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
ok:
|
||||
*enablep = enable;
|
||||
retval = 0;
|
||||
done:
|
||||
return retval;
|
||||
}
|
||||
|
||||
/*! Specialization of autocli_compress for extension element
|
||||
*/
|
||||
static int
|
||||
autocli_compress_extension(yang_stmt *ys,
|
||||
yang_stmt *ymod,
|
||||
char *body,
|
||||
int *match)
|
||||
{
|
||||
int retval = -1;
|
||||
char *prefix = NULL;
|
||||
char *id = NULL;
|
||||
char *ns = NULL;
|
||||
int exist = 0;
|
||||
int ret;
|
||||
|
||||
if (nodeid_split(body, &prefix, &id) < 0)
|
||||
goto done;
|
||||
if (prefix != NULL){
|
||||
if ((ret = yang_find_namespace_by_prefix(ys, prefix, &ns)) < 0)
|
||||
goto done;
|
||||
if (ret == 1){
|
||||
/* First try local node, then try module */
|
||||
if (yang_extension_value(ys, id, ns, &exist, NULL) < 0)
|
||||
goto done;
|
||||
if (exist == 0)
|
||||
if (yang_extension_value(ymod, id, ns, &exist, NULL) < 0)
|
||||
goto done;
|
||||
if (exist == 0)
|
||||
*match = 0;
|
||||
}
|
||||
}
|
||||
retval = 0;
|
||||
done:
|
||||
if (prefix)
|
||||
free(prefix);
|
||||
if (id)
|
||||
free(id);
|
||||
return retval;
|
||||
}
|
||||
|
||||
/*! Check if yang symbol shpuld be compressed, ie skipped for its child(ren)
|
||||
*
|
||||
* For each Traverse "compress" rule, check:
|
||||
* 1. Yang keyword match
|
||||
* 2. Schema-nodeid match (yang argument)
|
||||
* 3. module-name match
|
||||
* 4. Single list child?
|
||||
* The rules are OR:d, which means:
|
||||
* - At least one compress rules that match
|
||||
*
|
||||
* @param[in] h Clixon handle
|
||||
* @param[out] compress
|
||||
* @retval -1 Error
|
||||
* @retval 0 OK, and compress set
|
||||
|
||||
* Canonical examples:
|
||||
The config and state containers are "compressed" out of the schema.
|
||||
+ op=COMPRESS
|
||||
- node-id is config/state
|
||||
+ yang keyword is CONTAINER
|
||||
+ module-name=openconfig*
|
||||
|
||||
The surrounding container entities are removed from list nodes.
|
||||
+ op=COMPRESS
|
||||
+ yang keyword is CONTAINER
|
||||
- Only one child + Child keyword is LIST
|
||||
*/
|
||||
int
|
||||
autocli_compress(clicon_handle h,
|
||||
yang_stmt *ys,
|
||||
int *compress)
|
||||
{
|
||||
int retval = -1;
|
||||
cxobj *xautocli = NULL;
|
||||
cxobj *xrule;
|
||||
cxobj *xmod;
|
||||
yang_stmt *ymod;
|
||||
char *modname;
|
||||
char *str;
|
||||
char *element;
|
||||
char *nodeid;
|
||||
enum rfc_6020 keyw;
|
||||
char *keywstr;
|
||||
int match = 0;
|
||||
char *body;
|
||||
|
||||
if (compress == NULL){
|
||||
clicon_err(OE_YANG, EINVAL, "Argument is NULL");
|
||||
goto done;
|
||||
}
|
||||
if ((xautocli = clicon_conf_autocli(h)) == NULL){
|
||||
clicon_err(OE_YANG, 0, "No clixon-autocli");
|
||||
goto done;
|
||||
}
|
||||
ymod = ys_module(ys);
|
||||
modname = yang_argument_get(ymod);
|
||||
keyw = yang_keyword_get(ys);
|
||||
keywstr = yang_key2str(keyw);
|
||||
nodeid = yang_argument_get(ys);
|
||||
xrule = NULL;
|
||||
while ((xrule = xml_child_each(xautocli, xrule, CX_ELMNT)) != NULL) {
|
||||
if (strcmp(xml_name(xrule), "rule") != 0)
|
||||
continue;
|
||||
if ((str = xml_find_body(xrule, "operation")) == NULL)
|
||||
continue;
|
||||
/* Peek in element of rule to skip non-compress operations */
|
||||
if (autocli_str2op(str) != AUTOCLI_OP_COMPRESS)
|
||||
continue;
|
||||
/* At this point this rule is a compress rule
|
||||
* compress rule logic is "OR", the logic is:
|
||||
* - If match, break, done
|
||||
* - If not match, continue to next rule
|
||||
*/
|
||||
match = 1;
|
||||
xmod = NULL;
|
||||
while ((xmod = xml_child_each(xrule, xmod, CX_ELMNT)) != NULL) {
|
||||
if ((element = xml_name(xmod)) == NULL)
|
||||
continue;
|
||||
if (strcmp(element, "name") == 0 ||
|
||||
strcmp(element, "operation") == 0)
|
||||
continue;
|
||||
if ((body = xml_body(xmod)) == NULL)
|
||||
continue;
|
||||
if (strcmp(element, "yang-keyword") == 0){
|
||||
if (strcmp(body, keywstr) != 0){
|
||||
match = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if (strcmp(element, "schema-nodeid") == 0){
|
||||
if (strcmp(body, nodeid) != 0){
|
||||
match = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if (strcmp(element, "module-name") == 0){
|
||||
if (fnmatch(body, modname, 0) != 0){
|
||||
match = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else if (strcmp(element, "extension") == 0){
|
||||
if (autocli_compress_extension(ys, ymod, body, &match) < 0)
|
||||
goto done;
|
||||
if (match == 0)
|
||||
break;
|
||||
}
|
||||
else if (strcmp(element, "yang-keyword-child") == 0){
|
||||
enum rfc_6020 subkeyw;
|
||||
subkeyw = yang_str2key(body);
|
||||
if (yang_single_child_type(ys, subkeyw) == 0){
|
||||
match = 0;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (match) /* At least one compress rule matches */
|
||||
break;
|
||||
}
|
||||
*compress = match;
|
||||
retval = 0;
|
||||
done:
|
||||
return retval;
|
||||
}
|
||||
|
||||
/*! Return default autocli completion setting
|
||||
*
|
||||
* Currently only returns list-keyword-default, could be extended to rules
|
||||
* @param[in] h Clixon handle
|
||||
* @param[out] completion Completion enabled
|
||||
* @retval -1 Error
|
||||
* @retval 0 OK
|
||||
*/
|
||||
int
|
||||
autocli_completion(clicon_handle h,
|
||||
int *completion)
|
||||
{
|
||||
int retval = -1;
|
||||
char *str;
|
||||
uint8_t val;
|
||||
char *reason = NULL;
|
||||
int ret;
|
||||
cxobj *xautocli;
|
||||
|
||||
if (completion == NULL){
|
||||
clicon_err(OE_YANG, EINVAL, "Argument is NULL");
|
||||
goto done;
|
||||
}
|
||||
if ((xautocli = clicon_conf_autocli(h)) == NULL){
|
||||
clicon_err(OE_YANG, 0, "No clixon-autocli");
|
||||
goto done;
|
||||
}
|
||||
if ((str = xml_find_body(xautocli, "completion-default")) == NULL){
|
||||
clicon_err(OE_XML, EINVAL, "No completion-default rule");
|
||||
goto done;
|
||||
}
|
||||
if ((ret = parse_bool(str, &val, &reason)) < 0){
|
||||
clicon_err(OE_CFG, errno, "parse_bool");
|
||||
goto done;
|
||||
}
|
||||
*completion = val;
|
||||
retval = 0;
|
||||
done:
|
||||
if (reason)
|
||||
free(reason);
|
||||
return retval;
|
||||
}
|
||||
|
||||
/*! Return default autocli list keyword setting
|
||||
*
|
||||
* Currently only returns list-keyword-default, could be extended to rules
|
||||
* @param[in] h Clixon handle
|
||||
* @param[out] listkw List keyword setting
|
||||
* @retval -1 Error
|
||||
* @retval 0 OK
|
||||
*/
|
||||
int
|
||||
autocli_list_keyword(clicon_handle h,
|
||||
autocli_listkw_t *listkw)
|
||||
{
|
||||
int retval = -1;
|
||||
char *str;
|
||||
cxobj *xautocli = NULL;
|
||||
|
||||
if (listkw == NULL){
|
||||
clicon_err(OE_YANG, EINVAL, "Argument is NULL");
|
||||
goto done;
|
||||
}
|
||||
if ((xautocli = clicon_conf_autocli(h)) == NULL){
|
||||
clicon_err(OE_YANG, 0, "No clixon-autocli");
|
||||
goto done;
|
||||
}
|
||||
if ((str = xml_find_body(xautocli, "list-keyword-default")) == NULL){
|
||||
clicon_err(OE_XML, EINVAL, "No list-keyword-default rule");
|
||||
goto done;
|
||||
}
|
||||
*listkw = autocli_listkw_str2int(str);
|
||||
retval = 0;
|
||||
done:
|
||||
return retval;
|
||||
}
|
||||
|
||||
/*! Return default autocli treeref state setting, ie generate CLI from YANG non-config
|
||||
*
|
||||
* @param[in] h Clixon handle
|
||||
* @param[out] treeref_state If true, generate CLI from state
|
||||
* @retval -1 Error
|
||||
* @retval 0 OK
|
||||
*/
|
||||
int
|
||||
autocli_treeref_state(clicon_handle h,
|
||||
int *treeref_state)
|
||||
{
|
||||
int retval = -1;
|
||||
char *str;
|
||||
uint8_t val;
|
||||
char *reason = NULL;
|
||||
int ret;
|
||||
cxobj *xautocli;
|
||||
|
||||
if (treeref_state == NULL){
|
||||
clicon_err(OE_YANG, EINVAL, "Argument is NULL");
|
||||
goto done;
|
||||
}
|
||||
if ((xautocli = clicon_conf_autocli(h)) == NULL){
|
||||
clicon_err(OE_YANG, 0, "No clixon-autocli");
|
||||
goto done;
|
||||
}
|
||||
if ((str = xml_find_body(xautocli, "treeref-state-default")) == NULL){
|
||||
clicon_err(OE_XML, EINVAL, "No treeref-state-default rule");
|
||||
goto done;
|
||||
}
|
||||
if ((ret = parse_bool(str, &val, &reason)) < 0){
|
||||
clicon_err(OE_CFG, errno, "parse_bool");
|
||||
goto done;
|
||||
}
|
||||
*treeref_state = val;
|
||||
retval = 0;
|
||||
done:
|
||||
if (reason)
|
||||
free(reason);
|
||||
return retval;
|
||||
}
|
||||
|
||||
Loading…
Add table
Add a link
Reference in a new issue