* Configuration directory

* A new configuration option `CLICON_CONFIGDIR` has been added for loading of extra config files
  * If not given, only the main configfile is loaded.
  * If given, and if the directory exists, the files in this directory will be loaded alphabetically AFTER the main config file in the following way:
    * leaf values are overwritten
    * leaf-list values are appended
  * You can override file setting with `-E <dir>` command-line option.
* New clixon-config@2020-10-01.yang revision
  * Added option for configuration directory: `CLICON_CONFIGDIR`
This commit is contained in:
Olof hagsand 2020-10-01 11:04:25 +02:00
parent b3545871c0
commit 6f2c4a076d
11 changed files with 1245 additions and 80 deletions

View file

@ -37,11 +37,20 @@ Expected: 15 October 2020
* Verification of XPath functions is done at startup when yang modules are loaded, not when XPaths are evaluated. * Verification of XPath functions is done at startup when yang modules are loaded, not when XPaths are evaluated.
* Separation of "not found" and "not implemented" XPath functions * Separation of "not found" and "not implemented" XPath functions
* Both give a fatal error (backend does not start). * Both give a fatal error (backend does not start).
* Configuration directory
* A new configuration option `CLICON_CONFIGDIR` has been added for loading of extra config files
* If not given, only the main configfile is loaded.
* If given, and if the directory exists, the files in this directory will be loaded alphabetically AFTER the main config file in the following way:
* leaf values are overwritten
* leaf-list values are appended
* You can override file setting with `-E <dir>` command-line option.
### API changes on existing protocol/config features ### API changes on existing protocol/config features
Users may have to change how they access the system Users may have to change how they access the system
* New clixon-config@2020-10-01.yang revision
* Added option for configuration directory: `CLICON_CONFIGDIR`
* 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.
### Minor changes ### Minor changes
@ -59,7 +68,7 @@ Users may have to change how they access the system
14 September 2020 14 September 2020
This release is primarily a bugfix and usability improvement release, no major new features. This release is primarily a bugfix and usability improvement release, no major new features.
ppp
### API changes on existing protocol/config features ### API changes on existing protocol/config features
Users may have to change how they access the system Users may have to change how they access the system

View file

@ -76,7 +76,7 @@
#include "backend_startup.h" #include "backend_startup.h"
/* Command line options to be passed to getopt(3) */ /* Command line options to be passed to getopt(3) */
#define BACKEND_OPTS "hD:f:l:d:p:b:Fza:u:P:1qs:c:U:g:y:o:" #define BACKEND_OPTS "hD:f:E:l:d:p:b:Fza:u:P:1qs:c:U:g:y:o:"
#define BACKEND_LOGFILE "/usr/local/var/clixon_backend.log" #define BACKEND_LOGFILE "/usr/local/var/clixon_backend.log"
@ -402,6 +402,7 @@ usage(clicon_handle h,
"\t-h\t\tHelp\n" "\t-h\t\tHelp\n"
"\t-D <level>\tDebug level\n" "\t-D <level>\tDebug level\n"
"\t-f <file>\tCLICON config file\n" "\t-f <file>\tCLICON config file\n"
"\t-E <dir> \tExtra configuration file directory\n"
"\t-l (s|e|o|f<file>) Log on (s)yslog, std(e)rr or std(o)ut (stderr is default) Only valid if -F, if background syslog is on syslog.\n" "\t-l (s|e|o|f<file>) Log on (s)yslog, std(e)rr or std(o)ut (stderr is default) Only valid if -F, if background syslog is on syslog.\n"
"\t-d <dir>\tSpecify backend plugin directory (default: %s)\n" "\t-d <dir>\tSpecify backend plugin directory (default: %s)\n"
"\t-p <dir>\tYang directory path (see CLICON_YANG_DIR)\n" "\t-p <dir>\tYang directory path (see CLICON_YANG_DIR)\n"
@ -500,6 +501,11 @@ main(int argc,
usage(h, argv[0]); usage(h, argv[0]);
clicon_option_str_set(h, "CLICON_CONFIGFILE", optarg); clicon_option_str_set(h, "CLICON_CONFIGFILE", optarg);
break; break;
case 'E': /* extra config directory */
if (!strlen(optarg))
usage(h, argv[0]);
clicon_option_str_set(h, "CLICON_CONFIGDIR", optarg);
break;
case 'l': /* Log destination: s|e|o */ case 'l': /* Log destination: s|e|o */
if ((logdst = clicon_log_opt(optarg[0])) < 0) if ((logdst = clicon_log_opt(optarg[0])) < 0)
usage(h, argv[0]); usage(h, argv[0]);
@ -523,7 +529,7 @@ main(int argc,
if (clicon_options_main(h) < 0){ if (clicon_options_main(h) < 0){
if (help) if (help)
usage(h, argv[0]); usage(h, argv[0]);
return -1; goto done;
} }
/* External NACM file? */ /* External NACM file? */
nacm_mode = clicon_option_str(h, "CLICON_NACM_MODE"); nacm_mode = clicon_option_str(h, "CLICON_NACM_MODE");
@ -539,6 +545,7 @@ main(int argc,
case 'h' : /* help */ case 'h' : /* help */
case 'D' : /* debug */ case 'D' : /* debug */
case 'f': /* config file */ case 'f': /* config file */
case 'E': /* extra config dir */
case 'l' : case 'l' :
break; /* see above */ break; /* see above */
case 'd': /* Plugin directory */ case 'd': /* Plugin directory */

View file

@ -72,7 +72,7 @@
#include "cli_handle.h" #include "cli_handle.h"
/* Command line options to be passed to getopt(3) */ /* Command line options to be passed to getopt(3) */
#define CLI_OPTS "hD:f:l:F:1a:u:d:m:qp:GLy:c:U:o:" #define CLI_OPTS "hD:f:E:l:F:1a:u:d:m:qp:GLy:c:U:o:"
/*! Check if there is a CLI history file and if so dump the CLI histiry to it /*! Check if there is a CLI history file and if so dump the CLI histiry to it
* Just log if file does not exist or is not readable * Just log if file does not exist or is not readable
@ -361,6 +361,7 @@ usage(clicon_handle h,
"\t-h \t\tHelp\n" "\t-h \t\tHelp\n"
"\t-D <level> \tDebug level\n" "\t-D <level> \tDebug level\n"
"\t-f <file> \tConfig-file (mandatory)\n" "\t-f <file> \tConfig-file (mandatory)\n"
"\t-E <dir> \tExtra configuration file directory\n"
"\t-F <file> \tRead commands from file (default stdin)\n" "\t-F <file> \tRead commands from file (default stdin)\n"
"\t-1\t\tDo not enter interactive mode\n" "\t-1\t\tDo not enter interactive mode\n"
"\t-a UNIX|IPv4|IPv6\tInternal backend socket family\n" "\t-a UNIX|IPv4|IPv6\tInternal backend socket family\n"
@ -456,6 +457,11 @@ main(int argc,
usage(h, argv[0]); usage(h, argv[0]);
clicon_option_str_set(h, "CLICON_CONFIGFILE", optarg); clicon_option_str_set(h, "CLICON_CONFIGFILE", optarg);
break; break;
case 'E': /* extra config directory */
if (!strlen(optarg))
usage(h, argv[0]);
clicon_option_str_set(h, "CLICON_CONFIGDIR", optarg);
break;
case 'l': /* Log destination: s|e|o|f */ case 'l': /* Log destination: s|e|o|f */
if ((logdst = clicon_log_opt(optarg[0])) < 0) if ((logdst = clicon_log_opt(optarg[0])) < 0)
usage(h, argv[0]); usage(h, argv[0]);
@ -476,9 +482,8 @@ main(int argc,
if (clicon_options_main(h) < 0){ if (clicon_options_main(h) < 0){
if (help) if (help)
usage(h, argv[0]); usage(h, argv[0]);
return -1; goto done;
} }
/* Now rest of options */ /* Now rest of options */
opterr = 0; opterr = 0;
optind = 1; optind = 1;
@ -486,6 +491,7 @@ main(int argc,
switch (c) { switch (c) {
case 'D' : /* debug */ case 'D' : /* debug */
case 'f': /* config file */ case 'f': /* config file */
case 'E': /* extra config dir */
case 'l': /* Log destination */ case 'l': /* Log destination */
break; /* see above */ break; /* see above */
case 'F': /* read commands from file */ case 'F': /* read commands from file */

View file

@ -71,7 +71,7 @@
#include "netconf_rpc.h" #include "netconf_rpc.h"
/* Command line options to be passed to getopt(3) */ /* Command line options to be passed to getopt(3) */
#define NETCONF_OPTS "hD:f:l:qa:u:d:p:y:U:t:eo:" #define NETCONF_OPTS "hD:f:E:l:qa:u:d:p:y:U:t:eo:"
#define NETCONF_LOGFILE "/tmp/clixon_netconf.log" #define NETCONF_LOGFILE "/tmp/clixon_netconf.log"
@ -369,6 +369,7 @@ usage(clicon_handle h,
"\t-h\t\tHelp\n" "\t-h\t\tHelp\n"
"\t-D <level>\tDebug level\n" "\t-D <level>\tDebug level\n"
"\t-f <file>\tConfiguration file (mandatory)\n" "\t-f <file>\tConfiguration file (mandatory)\n"
"\t-E <dir> \tExtra configuration file directory\n"
"\t-l (e|o|s|f<file>) Log on std(e)rr, std(o)ut, (s)yslog(default), (f)ile\n" "\t-l (e|o|s|f<file>) Log on std(e)rr, std(o)ut, (s)yslog(default), (f)ile\n"
"\t-q\t\tQuiet: dont send hello prompt\n" "\t-q\t\tQuiet: dont send hello prompt\n"
"\t-a UNIX|IPv4|IPv6 Internal backend socket family\n" "\t-a UNIX|IPv4|IPv6 Internal backend socket family\n"
@ -434,6 +435,11 @@ main(int argc,
usage(h, argv[0]); usage(h, argv[0]);
clicon_option_str_set(h, "CLICON_CONFIGFILE", optarg); clicon_option_str_set(h, "CLICON_CONFIGFILE", optarg);
break; break;
case 'E': /* extra config directory */
if (!strlen(optarg))
usage(h, argv[0]);
clicon_option_str_set(h, "CLICON_CONFIGDIR", optarg);
break;
case 'l': /* Log destination: s|e|o */ case 'l': /* Log destination: s|e|o */
if ((logdst = clicon_log_opt(optarg[0])) < 0) if ((logdst = clicon_log_opt(optarg[0])) < 0)
usage(h, argv[0]); usage(h, argv[0]);
@ -452,7 +458,7 @@ main(int argc,
/* Find, read and parse configfile */ /* Find, read and parse configfile */
if (clicon_options_main(h) < 0) if (clicon_options_main(h) < 0)
return -1; goto done;
/* Now rest of options */ /* Now rest of options */
optind = 1; optind = 1;
@ -462,6 +468,7 @@ main(int argc,
case 'h' : /* help */ case 'h' : /* help */
case 'D' : /* debug */ case 'D' : /* debug */
case 'f': /* config file */ case 'f': /* config file */
case 'E': /* extra config dir */
case 'l': /* log */ case 'l': /* log */
break; /* see above */ break; /* see above */
case 'q': /* quiet: dont write hello */ case 'q': /* quiet: dont write hello */

View file

@ -80,7 +80,7 @@
#include "restconf_root.h" #include "restconf_root.h"
/* Command line options to be passed to getopt(3) */ /* Command line options to be passed to getopt(3) */
#define RESTCONF_OPTS "hD:f:l:p:d:y:a:u:ro:scP:" #define RESTCONF_OPTS "hD:f:E:l:p:d:y:a:u:ro:scP:"
/* See see listen(5) */ /* See see listen(5) */
#define SOCKET_LISTEN_BACKLOG 16 #define SOCKET_LISTEN_BACKLOG 16
@ -594,6 +594,7 @@ usage(clicon_handle h,
"\t-h \t\t Help\n" "\t-h \t\t Help\n"
"\t-D <level>\t Debug level\n" "\t-D <level>\t Debug level\n"
"\t-f <file>\t Configuration file (mandatory)\n" "\t-f <file>\t Configuration file (mandatory)\n"
"\t-E <dir> \t Extra configuration file directory\n"
"\t-l <s|f<file>> \t Log on (s)yslog, (f)ile (syslog is default)\n" "\t-l <s|f<file>> \t Log on (s)yslog, (f)ile (syslog is default)\n"
"\t-p <dir>\t Yang directory path (see CLICON_YANG_DIR)\n" "\t-p <dir>\t Yang directory path (see CLICON_YANG_DIR)\n"
"\t-d <dir>\t Specify restconf plugin directory dir (default: %s)\n" "\t-d <dir>\t Specify restconf plugin directory dir (default: %s)\n"
@ -666,6 +667,11 @@ main(int argc,
usage(h, argv0); usage(h, argv0);
clicon_option_str_set(h, "CLICON_CONFIGFILE", optarg); clicon_option_str_set(h, "CLICON_CONFIGFILE", optarg);
break; break;
case 'E': /* extra config directory */
if (!strlen(optarg))
usage(h, argv[0]);
clicon_option_str_set(h, "CLICON_CONFIGDIR", optarg);
break;
case 'l': /* Log destination: s|e|o */ case 'l': /* Log destination: s|e|o */
if ((logdst = clicon_log_opt(optarg[0])) < 0) if ((logdst = clicon_log_opt(optarg[0])) < 0)
usage(h, argv0); usage(h, argv0);
@ -715,6 +721,7 @@ main(int argc,
case 'h' : /* help */ case 'h' : /* help */
case 'D' : /* debug */ case 'D' : /* debug */
case 'f': /* config file */ case 'f': /* config file */
case 'E': /* extra config dir */
case 'l': /* log */ case 'l': /* log */
break; /* see above */ break; /* see above */
case 'p' : /* yang dir path */ case 'p' : /* yang dir path */

View file

@ -88,7 +88,7 @@
#include "restconf_stream.h" #include "restconf_stream.h"
/* Command line options to be passed to getopt(3) */ /* Command line options to be passed to getopt(3) */
#define RESTCONF_OPTS "hD:f:l:p:d:y:a:u:ro:" #define RESTCONF_OPTS "hD:f:E:l:p:d:y:a:u:ro:"
/*! Convert FCGI parameters to clixon runtime data /*! Convert FCGI parameters to clixon runtime data
* @param[in] h Clixon handle * @param[in] h Clixon handle
@ -172,6 +172,7 @@ usage(clicon_handle h,
"\t-h \t\t Help\n" "\t-h \t\t Help\n"
"\t-D <level>\t Debug level\n" "\t-D <level>\t Debug level\n"
"\t-f <file>\t Configuration file (mandatory)\n" "\t-f <file>\t Configuration file (mandatory)\n"
"\t-E <dir> \t Extra configuration file directory\n"
"\t-l <s|f<file>> \t Log on (s)yslog, (f)ile (syslog is default)\n" "\t-l <s|f<file>> \t Log on (s)yslog, (f)ile (syslog is default)\n"
"\t-p <dir>\t Yang directory path (see CLICON_YANG_DIR)\n" "\t-p <dir>\t Yang directory path (see CLICON_YANG_DIR)\n"
"\t-d <dir>\t Specify restconf plugin directory dir (default: %s)\n" "\t-d <dir>\t Specify restconf plugin directory dir (default: %s)\n"
@ -239,6 +240,11 @@ main(int argc,
usage(h, argv[0]); usage(h, argv[0]);
clicon_option_str_set(h, "CLICON_CONFIGFILE", optarg); clicon_option_str_set(h, "CLICON_CONFIGFILE", optarg);
break; break;
case 'E': /* extra config directory */
if (!strlen(optarg))
usage(h, argv[0]);
clicon_option_str_set(h, "CLICON_CONFIGDIR", optarg);
break;
case 'l': /* Log destination: s|e|o */ case 'l': /* Log destination: s|e|o */
if ((logdst = clicon_log_opt(optarg[0])) < 0) if ((logdst = clicon_log_opt(optarg[0])) < 0)
usage(h, argv[0]); usage(h, argv[0]);
@ -281,6 +287,7 @@ main(int argc,
case 'h' : /* help */ case 'h' : /* help */
case 'D' : /* debug */ case 'D' : /* debug */
case 'f': /* config file */ case 'f': /* config file */
case 'E': /* extra config dir */
case 'l': /* log */ case 'l': /* log */
break; /* see above */ break; /* see above */
case 'p' : /* yang dir path */ case 'p' : /* yang dir path */

View file

@ -50,6 +50,8 @@
#include <errno.h> #include <errno.h>
#include <stdlib.h> #include <stdlib.h>
#include <stdint.h> #include <stdint.h>
#include <dirent.h>
#include <libgen.h> /* dirname */
#include <syslog.h> #include <syslog.h>
#include <sys/types.h> #include <sys/types.h>
#include <sys/stat.h> #include <sys/stat.h>
@ -65,6 +67,7 @@
#include "clixon_queue.h" #include "clixon_queue.h"
#include "clixon_hash.h" #include "clixon_hash.h"
#include "clixon_handle.h" #include "clixon_handle.h"
#include "clixon_file.h"
#include "clixon_log.h" #include "clixon_log.h"
#include "clixon_yang.h" #include "clixon_yang.h"
#include "clixon_xml.h" #include "clixon_xml.h"
@ -190,51 +193,29 @@ clicon_option_dump(clicon_handle h,
return retval; return retval;
} }
/*! Read filename and set values to global options registry. XML variant. /*! Open and parse single config file
* * @param[in] filename
* @param[out] xconfig Pointer to xml config tree. Should be freed by caller * @param[in] yspec
* @retval 0 OK * @param[out] xconfig Pointer to xml config tree. Should be freed by caller
* @retval -1 Error
*/ */
static int static int
parse_configfile(clicon_handle h, parse_configfile_one(const char *filename,
const char *filename, yang_stmt *yspec,
yang_stmt *yspec, cxobj **xconfig)
cxobj **xconfig)
{ {
struct stat st; int retval = -1;
FILE *f = NULL; int fd = -1;
int retval = -1; cxobj *xt = NULL;
int fd; cxobj *xerr = NULL;
cxobj *xt = NULL; cxobj *xa;
cxobj *xc = NULL; cbuf *cbret = NULL;
cxobj *x = NULL; int ret;
char *name;
char *body;
clicon_hash_t *copt = clicon_options(h);
cbuf *cbret = NULL;
cxobj *xerr = NULL;
int ret;
cvec *nsc = NULL;
if (filename == NULL || !strlen(filename)){ if ((fd = open(filename, O_RDONLY)) < 0){
clicon_err(OE_UNIX, 0, "Not specified"); clicon_err(OE_UNIX, errno, "open configure file: %s", filename);
goto done;
}
if (stat(filename, &st) < 0){
clicon_err(OE_UNIX, errno, "%s", filename);
goto done;
}
if (!S_ISREG(st.st_mode)){
clicon_err(OE_UNIX, 0, "%s is not a regular file", filename);
goto done;
}
if ((f = fopen(filename, "r")) == NULL) {
clicon_err(OE_UNIX, errno, "configure file: %s", filename);
return -1; return -1;
} }
clicon_debug(2, "%s: Reading config file %s", __FUNCTION__, filename); clicon_debug(2, "%s: Reading config file %s", __FUNCTION__, filename);
fd = fileno(f);
if ((ret = clixon_xml_parse_file(fd, yspec?YB_MODULE:YB_NONE, yspec, NULL, &xt, &xerr)) < 0) if ((ret = clixon_xml_parse_file(fd, yspec?YB_MODULE:YB_NONE, yspec, NULL, &xt, &xerr)) < 0)
goto done; goto done;
if (ret == 0){ if (ret == 0){
@ -248,21 +229,137 @@ parse_configfile(clicon_handle h,
clixon_netconf_error(xerr, NULL, NULL); clixon_netconf_error(xerr, NULL, NULL);
goto done; goto done;
} }
if (xml_child_nr(xt)==1 && xml_child_nr_type(xt, CX_BODY)==1){ /* Ensure a single root */
clicon_err(OE_CFG, 0, "Config file %s: Expected XML but is probably old sh style", filename); if (xt == NULL || xml_child_nr(xt) != 1){
clicon_err(OE_CFG, 0, "Config file %s: Lacks single top element", filename);
goto done; goto done;
} }
/* Hard-coded config for < 3.10 and clixon-config for >= 3.10 */ if (xml_rootchild(xt, 0, &xt) < 0)
if ((nsc = xml_nsctx_init(NULL, CLIXON_CONF_NS)) == NULL)
goto done; goto done;
if ((xc = xpath_first(xt, nsc, "clixon-config")) == NULL){ /* Check well-formedness */
if (strcmp(xml_name(xt), "clixon-config") != 0 ||
(xa = xml_find_type(xt, NULL, "xmlns", CX_ATTR)) == NULL ||
strcmp(xml_value(xa), CLIXON_CONF_NS) != 0){
clicon_err(OE_CFG, 0, "Config file %s: Lacks top-level \"clixon-config\" element\nClixon config files should begin with: <clixon-config xmlns=\"%s\">", filename, CLIXON_CONF_NS); clicon_err(OE_CFG, 0, "Config file %s: Lacks top-level \"clixon-config\" element\nClixon config files should begin with: <clixon-config xmlns=\"%s\">", filename, CLIXON_CONF_NS);
goto done; goto done;
} }
if (xml_default_recurse(xc, 0) < 0) *xconfig = xt;
xt = NULL;
retval = 0;
done:
if (xt)
xml_free(xt);
if (fd != -1)
close(fd);
if (cbret)
cbuf_free(cbret);
if (xerr)
xml_free(xerr);
return retval;
}
/*! Read filename and set values to global options registry. XML variant.
*
* @param[in] h Clixon handle
* @param[in] filename Main configuration file
* @param[in] extraconfig0 Override (if set use that, othewrwise get from main file)
* @param[in] yspec Yang spec
* @param[out] xconfig Pointer to xml config tree. Should be freed by caller
* @retval 0 OK
* @retval -1 Error
*/
static int
parse_configfile(clicon_handle h,
const char *filename,
char *extraconfdir0,
yang_stmt *yspec,
cxobj **xconfig)
{
int retval = -1;
struct stat st;
cxobj *xt = NULL;
cxobj *xc = NULL;
cxobj *x = NULL;
char *name;
char *body;
clicon_hash_t *copt = clicon_options(h);
cbuf *cbret = NULL;
cxobj *xerr = NULL;
int ret;
cvec *nsc = NULL;
int i;
int ndp;
struct dirent *dp = NULL;
char filename1[MAXPATHLEN];
char *extraconfdir = NULL;
cxobj *xe = NULL;
cxobj *xec;
DIR *dirp;
if (filename == NULL || !strlen(filename)){
clicon_err(OE_UNIX, 0, "Not specified");
goto done;
}
if (stat(filename, &st) < 0){
clicon_err(OE_UNIX, errno, "%s", filename);
goto done;
}
if (!S_ISREG(st.st_mode)){
clicon_err(OE_UNIX, 0, "%s is not a regular file", filename);
goto done;
}
/* Parse main config file */
if (parse_configfile_one(filename, yspec, &xt) < 0)
goto done;
/* xt is a single-rooted: <clixon-config>...</clixon-config>
* If no override (eg from command-line)
* Bootstrap: Shortcut to read extra confdir inline */
if ((extraconfdir = extraconfdir0) == NULL)
if ((xc = xpath_first(xt, 0, "CLICON_CONFIGDIR")) != NULL)
extraconfdir = xml_body(xc);
if (extraconfdir){ /* If extra dir, parse extra config files */
/* A check it exists (also done in clicon_file_dirent) */
if ((dirp = opendir(extraconfdir)) == NULL) {
clicon_err(OE_UNIX, errno, "CLICON_CONFIGDIR: %s opendir", extraconfdir);
goto done;
}
closedir(dirp);
if((ndp = clicon_file_dirent(extraconfdir, &dp, NULL, S_IFREG)) < 0) /* Read dir */
goto done;
/* Loop through files */
for (i = 0; i < ndp; i++){
snprintf(filename1, sizeof(filename1), "%s/%s", extraconfdir, dp[i].d_name);
if (parse_configfile_one(filename1, yspec, &xe) < 0)
goto done;
/* Drain objects from extrafile and replace/append to main */
while ((xec = xml_child_i_type(xe, 0, CX_ELMNT)) != NULL) {
name = xml_name(xec);
body = xml_body(xec);
/* Ignored from file due to bootstrapping */
if (strcmp(name,"CLICON_CONFIGFILE")==0)
continue;
/* List options for configure options that are leaf-lists: append to main */
if (strcmp(name,"CLICON_FEATURE")==0 ||
strcmp(name,"CLICON_YANG_DIR")==0){
if (xml_addsub(xt, xec) < 0)
goto done;
continue;
}
/* Remove existing in master if any */
if ((x = xml_find_type(xt, NULL, name, CX_ELMNT)) != NULL)
xml_purge(x);
/* Append to master (removed from xe) */
if (xml_addsub(xt, xec) < 0)
goto done;
}
if (xe)
xml_free(xe);
xe = NULL;
}
}
if (xml_default_recurse(xt, 0) < 0)
goto done; goto done;
if ((ret = xml_yang_validate_add(h, xc, &xerr)) < 0) if ((ret = xml_yang_validate_add(h, xt, &xerr)) < 0)
goto done; goto done;
if (ret == 0){ if (ret == 0){
if ((cbret = cbuf_new()) ==NULL){ if ((cbret = cbuf_new()) ==NULL){
@ -274,34 +371,38 @@ parse_configfile(clicon_handle h,
clicon_err(OE_CFG, 0, "Config file validation: %s", cbuf_get(cbret)); clicon_err(OE_CFG, 0, "Config file validation: %s", cbuf_get(cbret));
goto done; goto done;
} }
while ((x = xml_child_each(xc, x, CX_ELMNT)) != NULL) { x = NULL;
while ((x = xml_child_each(xt, x, CX_ELMNT)) != NULL) {
name = xml_name(x); name = xml_name(x);
body = xml_body(x); body = xml_body(x);
if (name==NULL || body == NULL){ if (name == NULL || body == NULL){
clicon_log(LOG_WARNING, "%s option NULL: name:%s body:%s", clicon_log(LOG_WARNING, "%s option NULL: name:%s body:%s",
__FUNCTION__, name, body); __FUNCTION__, name, body);
continue; continue;
} }
/* hard-coded exceptions for configure options that are leaf-lists (not leaf) /* Ignored from file due to bootstrapping */
if (strcmp(name,"CLICON_CONFIGFILE")==0)
continue;
/* List options for configure options that are leaf-lists (not leaf)
* They must be accessed directly by looping over clicon_conf_xml(h) * They must be accessed directly by looping over clicon_conf_xml(h)
*/ */
if (strcmp(name,"CLICON_FEATURE")==0) if (strcmp(name,"CLICON_FEATURE")==0)
continue; continue;
if (strcmp(name,"CLICON_YANG_DIR")==0) if (strcmp(name,"CLICON_YANG_DIR")==0)
continue; continue;
/* Used as an arg to this fn */
if (strcmp(name,"CLICON_CONFIGFILE")==0)
continue;
if (clicon_hash_add(copt, if (clicon_hash_add(copt,
name, name,
body, body,
strlen(body)+1) == NULL) strlen(body)+1) == NULL)
goto done; goto done;
} }
retval = 0; retval = 0;
*xconfig = xt; *xconfig = xt;
xt = NULL; xt = NULL;
done: done:
if (dp)
free(dp);
if (nsc) if (nsc)
xml_nsctx_free(nsc); xml_nsctx_free(nsc);
if (cbret) if (cbret)
@ -310,8 +411,6 @@ parse_configfile(clicon_handle h,
xml_free(xerr); xml_free(xerr);
if (xt) if (xt)
xml_free(xt); xml_free(xt);
if (f)
fclose(f);
return retval; return retval;
} }
@ -375,6 +474,7 @@ clicon_options_main(clicon_handle h)
char xml = 0; /* Configfile is xml, otherwise legacy */ char xml = 0; /* Configfile is xml, otherwise legacy */
cxobj *xconfig = NULL; cxobj *xconfig = NULL;
yang_stmt *yspec = NULL; yang_stmt *yspec = NULL;
char *extraconfdir = NULL;
/* Create configure yang-spec */ /* Create configure yang-spec */
if ((yspec = yspec_new()) == NULL) if ((yspec = yspec_new()) == NULL)
@ -396,20 +496,27 @@ clicon_options_main(clicon_handle h)
clicon_err(OE_CFG, 0, "%s: suffix %s not recognized", configfile, suffix); clicon_err(OE_CFG, 0, "%s: suffix %s not recognized", configfile, suffix);
goto done; goto done;
} }
/* Read configfile first without yangspec, for bootstrapping, see second
* time below with proper yangspec. /* Override extraconfdir */
* (You need to read the config-file to get the YANG_DIR to find the if (clicon_option_str(h, "CLICON_CONFIGDIR") &&
* the clixon yang-spec) (extraconfdir = strdup(clicon_option_str(h, "CLICON_CONFIGDIR"))) == NULL){
clicon_err(OE_UNIX, errno, "strdup");
goto done;
}
/* Read configfile first without yangspec, and without extra config dir for bootstrapping,
* see second time below with proper yangspec and extra config dir
* (You need to read the config-file to get the YANG_DIR to find the clixon yang-spec)
* Difference from parsing with yangspec is: * Difference from parsing with yangspec is:
* - no default values * - no default values
* - no sanity checks * - no sanity checks
* - no extra config dir
*/ */
if (parse_configfile(h, configfile, NULL, &xconfig) < 0) if (parse_configfile(h, configfile, extraconfdir, NULL, &xconfig) < 0)
goto done; goto done;
if (xml_rootchild(xconfig, 0, &xconfig) < 0)
goto done;
/* Set clixon_conf pointer to handle */
clicon_conf_xml_set(h, xconfig); clicon_conf_xml_set(h, xconfig);
/* Parse clixon yang spec */ /* Parse clixon yang spec */
if (yang_spec_parse_module(h, "clixon-config", NULL, yspec) < 0) if (yang_spec_parse_module(h, "clixon-config", NULL, yspec) < 0)
goto done; goto done;
@ -418,10 +525,9 @@ clicon_options_main(clicon_handle h)
xml_free(xconfig); xml_free(xconfig);
xconfig = NULL; xconfig = NULL;
} }
/* Read configfile second time now with check yang spec */ /* Read configfile second time now with check yang spec */
if (parse_configfile(h, configfile, yspec, &xconfig) < 0) if (parse_configfile(h, configfile, extraconfdir, yspec, &xconfig) < 0)
goto done;
if (xml_rootchild(xconfig, 0, &xconfig) < 0)
goto done; goto done;
if (xml_spec(xconfig) == NULL){ if (xml_spec(xconfig) == NULL){
clicon_err(OE_CFG, 0, "Config file %s: did not find corresponding Yang specification\nHint: File does not begin with: <clixon-config xmlns=\"%s\"> or clixon-config.yang not found?", configfile, CLIXON_CONF_NS); clicon_err(OE_CFG, 0, "Config file %s: did not find corresponding Yang specification\nHint: File does not begin with: <clixon-config xmlns=\"%s\"> or clixon-config.yang not found?", configfile, CLIXON_CONF_NS);
@ -430,12 +536,17 @@ clicon_options_main(clicon_handle h)
/* Set yang config spec (must store to free at exit, since conf_xml below uses it) */ /* Set yang config spec (must store to free at exit, since conf_xml below uses it) */
if (clicon_config_yang_set(h, yspec) < 0) if (clicon_config_yang_set(h, yspec) < 0)
goto done; goto done;
yspec = NULL;
/* Set clixon_conf pointer to handle */ /* Set clixon_conf pointer to handle */
if (clicon_conf_xml_set(h, xconfig) < 0) if (clicon_conf_xml_set(h, xconfig) < 0)
goto done; goto done;
retval = 0; retval = 0;
done: done:
if (yspec)
ys_free(yspec);
if (extraconfdir)
free(extraconfdir);
return retval; return retval;
} }

128
test/test_configdir.sh Executable file
View file

@ -0,0 +1,128 @@
#!/usr/bin/env bash
# Test config file and extra config dir.
# Use clixon_cli and assume clixon_backend/restconf/netconf behaves the same
# Start without configdir as baseline
# Start with wrong configdir
# Start with empty configfile
# Start with 1 extra configfile
# Start with 2 extra configfiles
# Start with 2 extra configfiles + command-line
# Two options are used for testing:
# CLICON_MODULE_SET_ID is a single var (replaced)
# CLICON_FEATURE is a list var (append)
#
# 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
cdir=$dir/conf.d
cfile1=$cdir/00a.xml
cfile2=$cdir/01a.xml
test -d $cdir || mkdir $cdir
cat <<EOF > $cfg
<clixon-config xmlns="http://clicon.org/config">
<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>/usr/local/var/$APPNAME</CLICON_XMLDB_DIR>
<CLICON_YANG_DIR>/usr/local/share/clixon</CLICON_YANG_DIR>
<CLICON_CLISPEC_DIR>/usr/local/lib/$APPNAME/clispec</CLICON_CLISPEC_DIR>
<CLICON_CLI_DIR>/usr/local/lib/$APPNAME/cli</CLICON_CLI_DIR>
<CLICON_CLI_MODE>$APPNAME</CLICON_CLI_MODE>
<CLICON_MODULE_SET_ID>1</CLICON_MODULE_SET_ID>
<CLICON_FEATURE>test1</CLICON_FEATURE>
</clixon-config>
EOF
cat <<EOF > $cfile1
<clixon-config xmlns="http://clicon.org/config">
<CLICON_MODULE_SET_ID>2</CLICON_MODULE_SET_ID>
<CLICON_FEATURE>test2</CLICON_FEATURE>
</clixon-config>
EOF
new "Start without configdir as baseline"
expectpart "$($clixon_cli -1 -f $cfg show options)" 0 'CLICON_MODULE_SET_ID: "1"' 'CLICON_FEATURE: "test1"' --not-- 'CLICON_FEATURE: "test2"'
cat <<EOF > $cfg
<clixon-config xmlns="http://clicon.org/config">
<CLICON_CONFIGDIR>$dir/dontexist</CLICON_CONFIGDIR>
<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>/usr/local/var/$APPNAME</CLICON_XMLDB_DIR>
<CLICON_YANG_DIR>/usr/local/share/clixon</CLICON_YANG_DIR>
<CLICON_CLISPEC_DIR>/usr/local/lib/$APPNAME/clispec</CLICON_CLISPEC_DIR>
<CLICON_CLI_DIR>/usr/local/lib/$APPNAME/cli</CLICON_CLI_DIR>
<CLICON_CLI_MODE>$APPNAME</CLICON_CLI_MODE>
<CLICON_MODULE_SET_ID>1</CLICON_MODULE_SET_ID>
<CLICON_FEATURE>test1</CLICON_FEATURE>
</clixon-config>
EOF
new "Start with wrong configdir"
expectpart "$($clixon_cli -1 -f $cfg -l o show options)" 255 "UNIX error: CLICON_CONFIGDIR:" "opendir: No such file or directory"
rm -f $cfile1
cat <<EOF > $cfg
<clixon-config xmlns="http://clicon.org/config">
<CLICON_CONFIGDIR>$dir/notexist</CLICON_CONFIGDIR>
<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>/usr/local/var/$APPNAME</CLICON_XMLDB_DIR>
<CLICON_YANG_DIR>/usr/local/share/clixon</CLICON_YANG_DIR>
<CLICON_CLISPEC_DIR>/usr/local/lib/$APPNAME/clispec</CLICON_CLISPEC_DIR>
<CLICON_CLI_DIR>/usr/local/lib/$APPNAME/cli</CLICON_CLI_DIR>
<CLICON_CLI_MODE>$APPNAME</CLICON_CLI_MODE>
<CLICON_MODULE_SET_ID>1</CLICON_MODULE_SET_ID>
<CLICON_FEATURE>test1</CLICON_FEATURE>
</clixon-config>
EOF
new "Start with wrong configdir -E override"
expectpart "$($clixon_cli -1 -f $cfg -E $cdir show options)" 0 'CLICON_MODULE_SET_ID: "1"' 'CLICON_FEATURE: "test1"' --not-- 'CLICON_FEATURE: "test2"'
cat <<EOF > $cfg
<clixon-config xmlns="http://clicon.org/config">
<CLICON_CONFIGDIR>$cdir</CLICON_CONFIGDIR>
<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>/usr/local/var/$APPNAME</CLICON_XMLDB_DIR>
<CLICON_YANG_DIR>/usr/local/share/clixon</CLICON_YANG_DIR>
<CLICON_CLISPEC_DIR>/usr/local/lib/$APPNAME/clispec</CLICON_CLISPEC_DIR>
<CLICON_CLI_DIR>/usr/local/lib/$APPNAME/cli</CLICON_CLI_DIR>
<CLICON_CLI_MODE>$APPNAME</CLICON_CLI_MODE>
<CLICON_MODULE_SET_ID>1</CLICON_MODULE_SET_ID>
<CLICON_FEATURE>test1</CLICON_FEATURE>
</clixon-config>
EOF
new "Start with empty configdir"
expectpart "$($clixon_cli -1 -f $cfg -l o show options)" 0 'CLICON_MODULE_SET_ID: "1"' 'CLICON_FEATURE: "test1"' --not-- 'CLICON_FEATURE: "test2"'
cat <<EOF > $cfile1
<clixon-config xmlns="http://clicon.org/config">
<CLICON_MODULE_SET_ID>2</CLICON_MODULE_SET_ID>
<CLICON_FEATURE>test2</CLICON_FEATURE>
</clixon-config>
EOF
new "Start with 1 extra configfile"
expectpart "$($clixon_cli -1 -f $cfg -l o show options)" 0 'CLICON_MODULE_SET_ID: "2"' 'CLICON_FEATURE: "test1"' 'CLICON_FEATURE: "test2"'
cat <<EOF > $cfile2
<clixon-config xmlns="http://clicon.org/config">
<CLICON_MODULE_SET_ID>3</CLICON_MODULE_SET_ID>
<CLICON_FEATURE>test3</CLICON_FEATURE>
</clixon-config>
EOF
new "Start with 2 extra configfiles"
expectpart "$($clixon_cli -1 -f $cfg -l o show options)" 0 'CLICON_MODULE_SET_ID: "3"' 'CLICON_FEATURE: "test1"' 'CLICON_FEATURE: "test2"' 'CLICON_FEATURE: "test3"'
new "Start with 2 extra configfiles + command-line"
expectpart "$($clixon_cli -1 -f $cfg -o CLICON_MODULE_SET_ID=4 -o CLICON_FEATURE=test4 -l o show options)" 0 'CLICON_MODULE_SET_ID: "4"' 'CLICON_FEATURE: "test1"' 'CLICON_FEATURE: "test2"' 'CLICON_FEATURE: "test3"' 'CLICON_FEATURE: "test4"'
rm -rf $dir

View file

@ -42,7 +42,7 @@ datarootdir = @datarootdir@
# See also OPT_YANG_INSTALLDIR for the standard yang files # See also OPT_YANG_INSTALLDIR for the standard yang files
YANG_INSTALLDIR = @YANG_INSTALLDIR@ YANG_INSTALLDIR = @YANG_INSTALLDIR@
YANGSPECS = clixon-config@2020-08-17.yang YANGSPECS = clixon-config@2020-10-01.yang
YANGSPECS += clixon-lib@2020-04-23.yang YANGSPECS += clixon-lib@2020-04-23.yang
YANGSPECS += clixon-rfc5277@2008-07-01.yang YANGSPECS += clixon-rfc5277@2008-07-01.yang
YANGSPECS += clixon-xml-changelog@2019-03-21.yang YANGSPECS += clixon-xml-changelog@2019-03-21.yang

View file

@ -0,0 +1 @@
clixon-config@2020-10-01.yang

View file

@ -0,0 +1,882 @@
module clixon-config {
yang-version 1.1;
namespace "http://clicon.org/config";
prefix cc;
organization
"Clicon / Clixon";
contact
"Olof Hagsand <olof@hagsand.se>";
description
"Clixon configuration file
***** BEGIN LICENSE BLOCK *****
Copyright (C) 2009-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 *****";
revision 2020-10-01 {
description
"Added: CLICON_CONFIGDIR";
}
revision 2020-08-17 {
description
"Added: CLICON_RESTCONF_IPV4_ADDR, CLICON_RESTCONF_IPV6_ADDR,
CLICON_RESTCONF_HTTP_PORT, CLICON_RESTCONF_HTTPS_PORT
CLICON_NAMESPACE_NETCONF_DEFAULT,
CLICON_CLI_HELPSTRING_TRUNCATE, CLICON_CLI_HELPSTRING_LINES";
}
revision 2020-06-17 {
description
"Added: CLICON_CLI_LINES_DEFAULT
Added enum HIDE to CLICON_CLI_GENMODEL
Added CLICON_SSL_SERVER_CERT, CLICON_SSL_SERVER_KEY, CLICON_SSL_CA_CERT
Added CLICON_NACM_DISABLED_ON_EMPTY
Removed default valude of CLICON_NACM_RECOVERY_USER";
}
revision 2020-04-23 {
description
"Added: CLICON_YANG_UNKNOWN_ANYDATA to treat unknown XML (wrt YANG) as anydata.
Deleted: xml-stats non-config data (replaced by rpc stats in clixon-lib.yang)";
}
revision 2020-02-22 {
description
"Added: search index extension,
Added: clixon-stats state for clixon XML and memory statistics.
Added: CLICON_CLI_BUF_START and CLICON_CLI_BUF_THRESHOLD for quadratic and linear
growth of CLIgen buffers (cbuf:s)
Added: CLICON_VALIDATE_STATE_XML for controling validation of user state XML
Added: CLICON_CLICON_YANG_LIST_CHECK to skip list key checks";
}
revision 2019-09-11 {
description
"Added: CLICON_BACKEND_USER: drop of privileges to user,
CLICON_BACKEND_PRIVILEGES: how to drop privileges
CLICON_NACM_CREDENTIALS: If and how to check backend sock priveleges with NACM
CLICON_NACM_RECOVERY_USER: Name of NACM recovery user.";
}
revision 2019-06-05 {
description
"Added: CLICON_YANG_REGEXP, CLICON_CLI_TAB_MODE,
CLICON_CLI_HIST_FILE, CLICON_CLI_HIST_SIZE,
CLICON_XML_CHANGELOG, CLICON_XML_CHANGELOG_FILE;
Renamed CLICON_XMLDB_CACHE to CLICON_DATASTORE_CACHE (changed type)
Deleted: CLICON_XMLDB_PLUGIN, CLICON_USE_STARTUP_CONFIG";
}
revision 2019-03-05{
description
"Changed URN. Changed top-level symbol to clixon-config.
Released in Clixon 3.10";
}
revision 2019-02-06 {
description
"Released in Clixon 3.9";
}
revision 2018-10-21 {
description
"Released in Clixon 3.8";
}
extension search_index {
description "This list argument acts as a search index using optimized binary search.
";
}
typedef startup_mode{
description
"Which method to boot/start clicon backend.
The methods differ in how they reach a running state
Which source database to commit from, if any.";
type enumeration{
enum none{
description
"Do not touch running state
Typically after crash when running state and db are synched";
}
enum init{
description
"Initialize running state.
Start with a completely clean running state";
}
enum running{
description
"Commit running db configuration into running state
After reboot if a persistent running db exists";
}
enum startup{
description
"Commit startup configuration into running state
After reboot when no persistent running db exists";
}
}
}
typedef datastore_format{
description
"Datastore format.";
type enumeration{
enum xml{
description "Save and load xmldb as XML";
}
enum json{
description "Save and load xmldb as JSON";
}
}
}
typedef datastore_cache{
description
"XML configuration, ie running/candididate/ datastore cache behaviour.";
type enumeration{
enum nocache{
description "No cache always work directly with file";
}
enum cache{
description "Use in-memory cache.
Make copies when accessing internally.";
}
enum cache-zerocopy{
description "Use in-memory cache and dont copy.
Fastest but opens up for callbacks changing cache.";
}
}
}
typedef cli_genmodel_type{
description
"How to generate CLI from YANG model,
eg {container c {list a{ key x; leaf x; leaf y;}}";
type enumeration{
enum NONE{
description "No extra keywords: c a <x> <y>";
}
enum VARS{
description "Keywords on non-key variables: c a <x> y <y>";
}
enum ALL{
description "Keywords on all variables: c a x <x> y <y>";
}
enum HIDE{
description "Keywords on non-key variables and hide container around lists: a <x> y <y>";
}
}
}
typedef nacm_mode{
description
"Mode of RFC8341 Network Configuration Access Control Model.
It is unclear from the RFC whether NACM rules are internal
in a configuration (ie embedded in regular config) or external/OOB
in s separate, specific NACM-config";
type enumeration{
enum disabled{
description "NACM is disabled";
}
enum internal{
description "NACM is enabled and available in the regular config";
}
enum external{
description "NACM is enabled and available in a separate config";
}
}
}
typedef regexp_mode{
description
"The regular expression engine Clixon uses in its validation of
Yang patterns, and in the CLI.
Yang RFC 7950 stipulates XSD XML Schema regexps
according to W3 CXML Schema Part 2: Datatypes Second Edition,
see http://www.w3.org/TR/2004/REC-xmlschema-2-20041028#regexs";
type enumeration{
enum posix {
description
"Translate XSD XML Schema regexp:s to Posix regexp. This is
not a complete translation, but can be considered good-enough
for Yang use-cases as defined by openconfig and yang-models
for example.";
}
enum libxml2 {
description
"Use libxml2 XSD XML Schema regexp engine. This is a complete
XSD regexp engine..
Requires libxml2 to be available at configure time
(HAVE_LIBXML2 should be set)";
}
}
}
typedef priv_mode{
description
"Privilege mode, used for dropping (or not) priveleges to a non-provileged
user after initialization";
type enumeration{
enum none {
description
"Make no drop/change in privileges.";
}
enum drop_perm {
description
"After initialization, drop privileges permanently to a uid";
}
enum drop_temp {
description
"After initialization, drop privileges temporarily to a euid";
}
}
}
typedef nacm_cred_mode{
description
"How NACM user should be matched with unix socket peer credentials.
This means nacm user must match socket peer user accessing the
backend socket. For IP sockets only mode none makes sense.";
type enumeration{
enum none {
description
"Dont match NACM user to any user credentials. Any user can pose
as any other user. Set this for IP sockets, or dont use NACM.";
}
enum exact {
description
"Exact match between NACM user and unix socket peer user.";
}
enum except {
description
"Exact match between NACM user and unix socket peer user, except
for root and www user (restconf).";
}
}
}
container clixon-config {
leaf-list CLICON_FEATURE {
description
"Supported features as used by YANG feature/if-feature
value is: <module>:<feature>, where <module> and <feature>
are either names, or the special character '*'.
*:* means enable all features
<module>:* means enable all features in the specified module
*:<feature> means enable the specific feature in all modules";
type string;
}
leaf-list CLICON_YANG_DIR {
ordered-by user;
type string;
description
"Yang directory path for finding module and submodule files.
A list of these options should be in the configuration.
When loading a Yang module, Clixon searches this list in the order
they appear. Ensure that YANG_INSTALLDIR(default
/usr/local/share/clixon) is present in the path";
}
leaf CLICON_CONFIGFILE{
type string;
description
"Location of the main configuration-file.
Default is CLIXON_DEFAULT_CONFIG=/usr/local/etc/clicon.xml set in configure.
Note that due to bootstrapping, this value is not actually read from file
and therefore a default value would be meaningless.";
}
leaf CLICON_CONFIGDIR{
type string;
description
"Location of directory of extra configuration files.
If not given, only main configfile is read.
If given, and if the directory exists, all files in this directory will be loaded
AFTER the main config file (CLICON_CONFIGFILE) in the following way:
- leaf values are overwritten
- leaf-list values are appended
The files in this directory will be loaded alphabetically.
If the dir is given but does not exist will result in an error.
You can override file setting with -E <dir> command-line option.
Note that due to bootstraping this value is only meaningful in the main config file";
}
leaf CLICON_YANG_MAIN_FILE {
type string;
description
"If specified load a yang module in a specific absolute filename.
This corresponds to the -y command-line option in most CLixon
programs.";
}
leaf CLICON_YANG_MAIN_DIR {
type string;
description
"If given, load all modules in this directory (all .yang files)
See also CLICON_YANG_DIR which specifies a path of dirs";
}
leaf CLICON_YANG_MODULE_MAIN {
type string;
description
"Option used to construct initial yang file:
<module>[@<revision>]";
}
leaf CLICON_YANG_MODULE_REVISION {
type string;
description
"Option used to construct initial yang file:
<module>[@<revision>].
Used together with CLICON_YANG_MODULE_MAIN";
}
leaf CLICON_YANG_REGEXP {
type regexp_mode;
default posix;
description
"The regular expression engine Clixon uses in its validation of
Yang patterns, and in the CLI.
There is a 'good-enough' posix translation mode and a complete
libxml2 mode";
}
leaf CLICON_YANG_LIST_CHECK {
type boolean;
default true;
description
"If false, skip Yang list check sanity checks from RFC 7950, Sec 7.8.2:
The 'key' statement, which MUST be present if the list represents configuration.
Some yang specs seem not to fulfil this. However, if you reset this, there may
be follow-up errors due to code that assumes a configuration list has keys";
}
leaf CLICON_YANG_UNKNOWN_ANYDATA{
type boolean;
default false;
description
"Treat unknown XML/JSON nodes as anydata when loading from startup db.
This does not apply to namespaces, which means a top-level node: xxx:yyy
is accepted only if yyy is unknown, not xxx.
Note that this option has several caveats which needs to be fixed. Please
use with care.
The primary issue is that the unknown->anydata handling is not restricted to
only loading from startup but may occur in other circumstances as well. This
means that sanity checks of erroneous XML/JSON may not be properly signalled.";
}
leaf CLICON_BACKEND_DIR {
type string;
description
"Location of backend .so plugins. Load all .so
plugins in this dir as backend plugins";
}
leaf CLICON_BACKEND_REGEXP {
type string;
description
"Regexp of matching backend plugins in CLICON_BACKEND_DIR";
default "(.so)$";
}
leaf CLICON_NETCONF_DIR {
type string;
description "Location of netconf (frontend) .so plugins";
}
leaf CLICON_RESTCONF_DIR {
type string;
description
"Location of restconf (frontend) .so plugins. Load all .so
plugins in this dir as restconf code plugins";
}
leaf CLICON_RESTCONF_PATH {
type string;
default "/www-data/fastcgi_restconf.sock";
description
"FastCGI unix socket. Should be specified in webserver
Eg in nginx: fastcgi_pass unix:/www-data/clicon_restconf.sock";
}
leaf CLICON_RESTCONF_PRETTY {
type boolean;
default true;
description
"Restconf return value pretty print.
Restconf clients may add HTTP header:
Accept: application/yang-data+json, or
Accept: application/yang-data+xml
to get return value in XML or JSON.
RFC 8040 examples print XML and JSON in pretty-printed form.
Setting this value to false makes restconf return not pretty-printed
which may be desirable for performance or tests";
}
leaf CLICON_RESTCONF_IPV4_ADDR {
type string;
default "0.0.0.0";
description
"RESTCONF IPv4 socket binding address.
Applies to native http by config option --with-restconf=evhtp.";
}
leaf CLICON_RESTCONF_IPV6_ADDR {
type string;
default "::";
description
"RESTCONF IPv6 socket binding address.
Applies to native http by config option --with-restconf=evhtp.";
}
leaf CLICON_RESTCONF_HTTP_PORT {
type uint16;
default 80;
description
"RESTCONF socket binding port, non-ssl
In the restconf daemon, it can be overriden by -P <port>
Applies to native http only by config option --with-restconf=evhtp.";
}
leaf CLICON_RESTCONF_HTTPS_PORT {
type uint16;
default 443;
description
"RESTCONF socket binding port, ssl
In the restconf daemon, this is the port chosen if -s is given.
Note it can be overriden by -P <port>
Applies to native http by config option --with-restconf=evhtp.";
}
leaf CLICON_SSL_SERVER_CERT {
type string;
default "/etc/ssl/certs/clixon-server-crt.pem";
description
"SSL server cert for restconf https.
Applies to native http only by config option --with-restconf=evhtp.";
}
leaf CLICON_SSL_SERVER_KEY {
type string;
default "/etc/ssl/private/clixon-server-key.pem";
description
"SSL server private key for restconf https.
Applies to native http only by config option --with-restconf=evhtp.";
}
leaf CLICON_SSL_CA_CERT {
type string;
default "/etc/ssl/certs/clixon-ca_crt.pem";
description
"SSL CA cert for client authentication.
Applies to native http only by config option --with-restconf=evhtp.";
}
leaf CLICON_CLI_DIR {
type string;
description
"Directory containing frontend cli loadable plugins. Load all .so
plugins in this directory as CLI object plugins";
}
leaf CLICON_CLISPEC_DIR {
type string;
description
"Directory containing frontend cligen spec files. Load all .cli
files in this directory as CLI specification files.
See also CLICON_CLISPEC_FILE.";
}
leaf CLICON_CLISPEC_FILE {
type string;
description
"Specific frontend cligen spec file as aletrnative or complement
to CLICON_CLISPEC_DIR. Also available as -c in clixon_cli.";
}
leaf CLICON_CLI_MODE {
type string;
default "base";
description
"Startup CLI mode. This should match a CLICON_MODE variable set in
one of the clispec files";
}
leaf CLICON_CLI_GENMODEL {
type int32;
default 1;
description
"0: Do not generate CLISPEC syntax for the auto-cli.
1: Generate a CLI specification for CLI completion of all loaded Yang modules.
This CLI tree can be accessed in CLI-spec files using the tree reference syntax (eg
@datamodel).
2: Same including state syntax in a tree called @datamodelstate.
See also CLICON_CLI_MODEL_TREENAME.";
}
leaf CLICON_CLI_MODEL_TREENAME {
type string;
default "datamodel";
description
"If set, CLI specs can reference the
model syntax using this reference.
Example: set @datamodel, cli_set();
A second tree called eg @datamodelstate is created that
also contains state together with config.";
}
leaf CLICON_CLI_GENMODEL_COMPLETION {
type int32;
default 1;
description "Generate code for CLI completion of existing db symbols.
(consider boolean)";
}
leaf CLICON_CLI_GENMODEL_TYPE {
type cli_genmodel_type;
default "VARS";
description "How to generate and show CLI syntax: VARS|ALL";
}
leaf CLICON_CLI_VARONLY {
type int32;
default 1;
description
"Dont include keys in cvec in cli vars callbacks,
ie a & k in 'a <b> k <c>' ignored
(consider boolean)";
}
leaf CLICON_CLI_LINESCROLLING {
type int32;
default 1;
description
"Set to 0 if you want CLI to wrap to next line.
Set to 1 if you want CLI to scroll sideways when approaching
right margin";
}
leaf CLICON_CLI_LINES_DEFAULT {
type int32;
default 24;
description
"Set to number of CLI terminal rows for pageing/scrolling. 0 means unlimited.
The number is set statically UNLESS:
- there is no terminal, such as file input, in which case nr lines is 0
- there is a terminal sufficiently powerful to read the number of lines from
ioctl calls.
In other words, this setting is used ONLY on raw terminals such as serial
consoles.";
}
leaf CLICON_CLI_TAB_MODE {
type int8;
default 0;
description
"Set CLI tab mode. This is actually a bitfield of three
combinations:
bit 1: 0: <tab> shows short info of available commands
1: <tab> has same output as <?>, ie line per command
bit 2: 0: On <tab>, select a command over a <var> if both exist
1: Commands and vars have same preference.
bit 3: 0: On <tab>, never complete more than one level per <tab>
1: Complete all levels at once if possible.
";
}
leaf CLICON_CLI_UTF8 {
type int8;
default 0;
description
"Set to 1 to enable CLIgen UTF-8 experimental mode.
Note that this feature is EXPERIMENTAL and may not properly handle
scrolling, control characters, etc
(consider boolean)";
}
leaf CLICON_CLI_HIST_FILE {
type string;
default "~/.clixon_cli_history";
description
"Name of CLI history file. If not given, history is not saved.
The number of lines is saved is given by CLICON_CLI_HIST_SIZE.";
}
leaf CLICON_CLI_HIST_SIZE {
type int32;
default 300;
description
"Number of lines to save in CLI history.
Also, if CLICON_CLI_HIST_FILE is set, also the size in lines
of the saved history.";
}
leaf CLICON_CLI_BUF_START {
type uint32;
default 256;
description
"CLIgen buffer (cbuf) initial size.
When the buffer needs to grow, the allocation grows quadratic up to a threshold
after which linear growth continues.
See CLICON_CLI_BUF_THRESHOLD";
}
leaf CLICON_CLI_BUF_THRESHOLD {
type uint32;
default 65536;
description
"CLIgen buffer (cbuf) threshold size.
When the buffer exceeds the threshold, the allocation grows by adding the threshold
value to the buffer length.
If 0, the growth continues with quadratic growth.
See CLICON_CLI_BUF_THRESHOLD";
}
leaf CLICON_CLI_HELPSTRING_TRUNCATE {
type boolean;
default false;
description
"CLIgen help string on query (?): Truncate help string on right margin mode
This only applies if you have long help strings, such as when generating them from a
spec such as the autocli";
}
leaf CLICON_CLI_HELPSTRING_LINES {
type int32;
default 0;
description
"CLIgen help string on query (?) limit of number of lines to show, 0 means unlimited.
This only applies if you have multi-line help strings, such as when generating
from a spec, such as in the autocli.";
}
leaf CLICON_SOCK_FAMILY {
type string;
default "UNIX";
description
"Address family for communicating with clixon_backend
(UNIX|IPv4). IPv6 not yet implemented.
Note that UNIX socket makes credential check as follows:
(1) client needs rw access to the socket
(2) NACM credentials can be checked according to CLICON_NACM_CREDENTIALS
Warning: IPv4 and IPv6 sockets have no credential mechanism.
";
}
leaf CLICON_SOCK {
type string;
mandatory true;
description
"If family above is AF_UNIX: Unix socket for communicating
with clixon_backend. If family is AF_INET: IPv4 address";
}
leaf CLICON_SOCK_PORT {
type int32;
default 4535;
description
"Inet socket port for communicating with clixon_backend
(only IPv4|IPv6)";
}
leaf CLICON_SOCK_GROUP {
type string;
default "clicon";
description
"Group membership to access clixon_backend unix socket and gid for
deamon";
}
leaf CLICON_BACKEND_USER {
type string;
description
"User name for backend (both foreground and daemonized).
If you set this value the backend if started as root will lower
the privileges after initialization.
The ownership of files created by the backend will also be set to this
user (eg datastores).
It also sets the backend unix socket owner to this user, but its group
is set by CLICON_SOCK_GROUP.
See also CLICON_PRIVILEGES setting";
}
leaf CLICON_BACKEND_PRIVILEGES {
type priv_mode;
default none;
description
"Backend privileges mode.
If CLICON_BACKEND_USER user is set, mode can be set to drop_perm or
drop_temp.";
}
leaf CLICON_BACKEND_PIDFILE {
type string;
mandatory true;
description "Process-id file of backend daemon";
}
leaf CLICON_AUTOCOMMIT {
type int32;
default 0;
description
"Set if all configuration changes are committed automatically
on every edit change. Explicit commit commands unnecessary
(consider boolean)";
}
leaf CLICON_XMLDB_DIR {
type string;
mandatory true;
description
"Directory where \"running\", \"candidate\" and \"startup\" are placed.";
}
leaf CLICON_DATASTORE_CACHE {
type datastore_cache;
default cache;
description
"Clixon datastore cache behaviour. There are three values: no cache,
cache with copy, or cache without copy.";
}
leaf CLICON_XMLDB_FORMAT {
type datastore_format;
default xml;
description "XMLDB datastore format.";
}
leaf CLICON_XMLDB_PRETTY {
type boolean;
default true;
description
"XMLDB datastore pretty print.
If set, insert spaces and line-feeds making the XML/JSON human
readable. If not set, make the XML/JSON more compact.";
}
leaf CLICON_XMLDB_MODSTATE {
type boolean;
default false;
description
"If set, tag datastores with RFC 7895 YANG Module Library
info. When loaded at startup, a check is made if the system
yang modules match.
See also CLICON_MODULE_LIBRARY_RFC7895";
}
leaf CLICON_XML_CHANGELOG {
type boolean;
default false;
description "If true enable automatic upgrade using yang clixon
changelog.";
}
leaf CLICON_XML_CHANGELOG_FILE {
type string;
description "Name of file with module revision changelog.
If CLICON_XML_CHANGELOG is true, Clixon
reads the module changelog from this file.";
}
leaf CLICON_VALIDATE_STATE_XML {
type boolean;
default false;
description
"Validate user state callback content.
Users may register state callbacks using ca_statedata callback
When set, the XML returned from the callback is validated after merging with
the running db. If it fails, an internal error is returned to the originating
user.
If the option is not set, the XML returned by the user is not validated.
Note that enabling currently causes a large performance overhead for large
lists, therefore it is recommended to enable it during development and debugging
but disable it in production, until this has been resolved.";
}
leaf CLICON_NAMESPACE_NETCONF_DEFAULT {
type boolean;
default false;
description
"Undefine if you want to ensure strict namespace assignment on all netconf
and XML statements according to the standard RFC 6241.
If defined, top-level rpc calls need not have namespaces (eg using xmlns=<ns>)
since the default NETCONF namespace will be assumed. (This is not standard).
See rfc6241 3.1: urn:ietf:params:xml:ns:netconf:base:1.0.";
}
leaf CLICON_STARTUP_MODE {
type startup_mode;
description "Which method to boot/start clicon backend";
}
leaf CLICON_TRANSACTION_MOD {
type boolean;
default false;
description "If set, modifications in validation and commit
callbacks are written back into the datastore.
This is a bad idea and therefore obsoleted.";
status obsolete;
}
leaf CLICON_NACM_MODE {
type nacm_mode;
default disabled;
description
"RFC8341 network access configuration control model (NACM) mode: disabled,
in regular (internal) config or separate external file given by CLICON_NACM_FILE";
}
leaf CLICON_NACM_FILE {
type string;
description
"RFC8341 NACM external configuration file (if CLIXON_NACM_MODE is external)";
}
leaf CLICON_NACM_CREDENTIALS {
type nacm_cred_mode;
default except;
description
"Verify nacm user credentials with unix socket peer cred.
This means nacm user must match unix user accessing the backend
socket.";
}
leaf CLICON_NACM_RECOVERY_USER {
type string;
description
"RFC8341 defines a 'recovery session' as outside its scope. Clixon
defines this user as having special admin rights to exempt from
all access control enforcements.
Note setting of CLICON_NACM_CREDENTIALS is important, if set to
exact for example, this user must exist and be used, otherwise
another user (such as root or www) can pose as the recovery user.";
}
leaf CLICON_NACM_DISABLED_ON_EMPTY {
type boolean;
default false;
description
"RFC 8341 and ietf-netconf-acm@2018-02-14.yang defines enable-nacm as true by
default. Since also write-default is deny by default it leads to that empty
configs can not be edited.
This means that a startup config must always have a NACM configuration or
that the NACM recovery session is used to edit an empty config.
If this option is set, Clixon disables NACM if a datastore does NOT contain a
NACM config on load.";
}
leaf CLICON_MODULE_LIBRARY_RFC7895 {
type boolean;
default true;
description
"Enable RFC 7895 YANG Module library support as state data. If
enabled, module info will appear when doing netconf get or
restconf GET.
See also CLICON_XMLDB_MODSTATE";
}
leaf CLICON_MODULE_SET_ID {
type string;
default "0";
description "If RFC 7895 YANG Module library enabled:
Contains a server-specific identifier representing
the current set of modules and submodules. The
server MUST change the value of this leaf if the
information represented by the 'module' list instances
has changed.";
}
leaf CLICON_STREAM_DISCOVERY_RFC5277 {
type boolean;
default false;
description "Enable event stream discovery as described in RFC 5277
sections 3.2. If enabled, available streams will appear
when doing netconf get or restconf GET";
}
leaf CLICON_STREAM_DISCOVERY_RFC8040 {
type boolean;
default false;
description
"Enable monitoring information for the RESTCONF protocol from RFC 8040";
}
leaf CLICON_STREAM_PATH {
type string;
default "streams";
description "Stream path appended to CLICON_STREAM_URL to form
stream subscription URL.";
}
leaf CLICON_STREAM_URL {
type string;
default "https://localhost";
description "Prepend this to CLICON_STREAM_PATH to form URL.
See RFC 8040 Sec 9.3 location leaf:
'Contains a URL that represents the entry point for
establishing notification delivery via server-sent events.'
Prepend this constant to name of stream.
Example: https://localhost/streams/NETCONF. Note this is the
external URL, not local behind a reverse-proxy.
Note that -s <stream> command-line option to clixon_restconf
should correspond to last path of url (eg 'streams')";
}
leaf CLICON_STREAM_PUB {
type string;
description "For stream publish using eg nchan, the base address
to publish to. Example value: http://localhost/pub
Example: stream NETCONF would then be pushed to
http://localhost/pub/NETCONF.
Note this may be a local/provate URL behind reverse-proxy.
If not given, do NOT enable stream publishing using NCHAN.";
}
leaf CLICON_STREAM_RETENTION {
type uint32;
default 3600;
units s;
description "Retention for stream replay buffers in seconds, ie how much
data to store before dropping. 0 means no retention";
}
}
}