Moved hello example to [clixon-examples](https://github.com/clicon/clixon-examples)

This commit is contained in:
Olof hagsand 2020-03-11 11:41:24 +01:00
parent adbb683329
commit d7972ff311
17 changed files with 208 additions and 383 deletions

View file

@ -75,6 +75,7 @@ Expected: Early March 2020
### Minor changes
* Moved hello example to [clixon-examples](https://github.com/clicon/clixon-examples)
* Sanity check of mandatory key statement for Yang LISTs.
* If fails, exit with error message, eg: `Yang error: Sanity check failed: LIST vsDataContainer lacks key statement which MUST be present (See RFC 7950 Sec 7.8.2)`
* Can be disabled by setting `CLICON_CLICON_YANG_LIST_CHECK` to `false`

3
configure vendored
View file

@ -4978,7 +4978,7 @@ _ACEOF
ac_config_files="$ac_config_files Makefile lib/Makefile lib/src/Makefile lib/clixon/Makefile apps/Makefile apps/cli/Makefile apps/backend/Makefile apps/netconf/Makefile apps/restconf/Makefile include/Makefile etc/Makefile etc/clixonrc example/Makefile example/main/Makefile example/hello/Makefile extras/rpm/Makefile docker/Makefile docker/main/Makefile docker/base/Makefile util/Makefile yang/Makefile yang/clixon/Makefile yang/mandatory/Makefile yang/optional/Makefile doc/Makefile test/Makefile test/cicd/Makefile"
ac_config_files="$ac_config_files Makefile lib/Makefile lib/src/Makefile lib/clixon/Makefile apps/Makefile apps/cli/Makefile apps/backend/Makefile apps/netconf/Makefile apps/restconf/Makefile include/Makefile etc/Makefile etc/clixonrc example/Makefile example/main/Makefile extras/rpm/Makefile docker/Makefile docker/main/Makefile docker/base/Makefile util/Makefile yang/Makefile yang/clixon/Makefile yang/mandatory/Makefile yang/optional/Makefile doc/Makefile test/Makefile test/cicd/Makefile"
cat >confcache <<\_ACEOF
# This file is a shell script that caches the results of configure
@ -5686,7 +5686,6 @@ do
"etc/clixonrc") CONFIG_FILES="$CONFIG_FILES etc/clixonrc" ;;
"example/Makefile") CONFIG_FILES="$CONFIG_FILES example/Makefile" ;;
"example/main/Makefile") CONFIG_FILES="$CONFIG_FILES example/main/Makefile" ;;
"example/hello/Makefile") CONFIG_FILES="$CONFIG_FILES example/hello/Makefile" ;;
"extras/rpm/Makefile") CONFIG_FILES="$CONFIG_FILES extras/rpm/Makefile" ;;
"docker/Makefile") CONFIG_FILES="$CONFIG_FILES docker/Makefile" ;;
"docker/main/Makefile") CONFIG_FILES="$CONFIG_FILES docker/main/Makefile" ;;

View file

@ -277,7 +277,6 @@ AC_OUTPUT(Makefile
etc/clixonrc
example/Makefile
example/main/Makefile
example/hello/Makefile
extras/rpm/Makefile
docker/Makefile
docker/main/Makefile

View file

@ -40,7 +40,7 @@ LIBS = @LIBS@
SHELL = /bin/sh
SUBDIRS = main hello
SUBDIRS = main
.PHONY: all clean depend install $(SUBDIRS)

View file

@ -1,5 +1,9 @@
# Clixon examples
Clixon have the following examples:
* [Hello world](hello/README.md)
See the separate
[clixon-examples](https://github.com/clicon/clixon-examples) repo.
The only Clixon example remaining is for internal testing:
* [Main example](main/README.md)

View file

@ -1,168 +0,0 @@
#
# ***** BEGIN LICENSE BLOCK *****
#
# Copyright (C) 2009-2019 Olof Hagsand and Benny Holmgren
#
# 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 *****
#
VPATH = @srcdir@
srcdir = @srcdir@
top_srcdir = @top_srcdir@
prefix = @prefix@
bindir = @bindir@
includedir = @includedir@
datarootdir = @datarootdir@
sysconfdir = @sysconfdir@
datarootdir = @datarootdir@
localstatedir = @localstatedir@
libdir = @exec_prefix@/lib
APPNAME = hello
# Here is where example yang appears
YANG_INSTALLDIR = @YANG_INSTALLDIR@
# Install here if you want default clixon location:
CLIXON_DEFAULT_CONFIG = @CLIXON_DEFAULT_CONFIG@
CC = @CC@
CFLAGS = @CFLAGS@ -fPIC
INSTALLFLAGS = @INSTALLFLAGS@
with_restconf = @with_restconf@
INCLUDES = -I$(includedir) @INCLUDES@
CPPFLAGS = @CPPFLAGS@ -fPIC
BE_PLUGIN = $(APPNAME)_backend.so
CLI_PLUGIN = $(APPNAME)_cli.so
NETCONF_PLUGIN = $(APPNAME)_netconf.so
RESTCONF_PLUGIN = $(APPNAME)_restconf.so
PLUGINS = $(BE_PLUGIN) $(CLI_PLUGIN) $(NETCONF_PLUGIN)
ifeq ($(with_restconf),yes)
PLUGINS += $(RESTCONF_PLUGIN)
endif
.PHONY: all clean depend install
all: $(PLUGINS)
.SUFFIXES: .c .o
# implicit rule
.c.o:
$(CC) $(INCLUDES) $(CPPFLAGS) $(CFLAGS) -c $<
CLISPECS = $(APPNAME)_cli.cli
YANGSPECS = clixon-hello@2019-04-17.yang
# CLI frontend plugin (see also install rule)
#CLI_SRC = $(APPNAME)_cli.c
CLI_OBJ = $(CLI_SRC:%.c=%.o)
$(CLI_PLUGIN): $(CLI_OBJ)
$(CC) -Wall -shared -o $@ -lc $^
# Backend plugin (see also install rule)
#BE_SRC = $(APPNAME)_backend.c
BE_OBJ = $(BE_SRC:%.c=%.o)
$(BE_PLUGIN): $(BE_OBJ)
$(CC) -Wall -shared -o $@ -lc $<
# NETCONF frontend plugin (see also install rule)
#NETCONF_SRC = $(APPNAME)_netconf.c
NETCONF_OBJ = $(NETCONF_SRC:%.c=%.o)
$(NETCONF_PLUGIN): $(NETCONF_OBJ)
$(CC) -Wall -shared -o $@ -lc $^
# See configure.ac for disabling restconf
# RESTCONF frontend plugin (see also install rule)
#RESTCONF_SRC = $(APPNAME)_restconf.c
RESTCONF_OBJ = $(RESTCONF_SRC:%.c=%.o)
$(RESTCONF_PLUGIN): $(RESTCONF_OBJ)
$(CC) -Wall -shared -o $@ -lc $^
SRC = $(BE_SRC) $(CLI_SRC) $(NETCONF_SRC)
SRC += $(RESTCONF_SRC)
OBJS = $(BE_OBJ) $(CLI_OBJ) $(NETCONF_OBJ)
OBJS += $(RESTCONF_OBJ)
clean:
rm -f $(PLUGINS) $(OBJS)
distclean: clean
rm -f Makefile *~ .depend
install: $(YANGSPECS) $(CLISPECS) $(PLUGINS) $(APPNAME).xml
install -d -m 0755 $(dir $(DESTDIR)$(CLIXON_DEFAULT_CONFIG))
install -m 0644 $(APPNAME).xml $(DESTDIR)$(CLIXON_DEFAULT_CONFIG)
install -d -m 0755 $(DESTDIR)$(libdir)/$(APPNAME)
install -d -m 0755 $(DESTDIR)$(libdir)/$(APPNAME)/clispec
install -m 0644 $(CLISPECS) $(DESTDIR)$(libdir)/$(APPNAME)/clispec
install -d -m 0755 $(DESTDIR)$(datarootdir)/$(APPNAME)/yang
install -d -m 0755 $(DESTDIR)$(YANG_INSTALLDIR)
install -m 0644 $(YANGSPECS) $(DESTDIR)$(YANG_INSTALLDIR)
install -d -m 0755 $(DESTDIR)$(localstatedir)/$(APPNAME)
# Uncomment for installing config file in /usr/local/etc instead
# install -d -m 0755 $(DESTDIR)$(sysconfdir)
# install -m 0644 $(APPNAME).xml $(DESTDIR)$(sysconfdir)
# Uncomment for installing cli plugin (see CLI_SRC)
# install -d -m 0755 $(DESTDIR)$(libdir)/$(APPNAME)/cli
# install -m 0644 $(INSTALLFLAGS) $(CLI_PLUGIN) $(DESTDIR)$(libdir)/$(APPNAME)/cli
# Uncomment for installing backend plugin (see BE_SRC)
# install -d -m 0755 $(DESTDIR)$(libdir)/$(APPNAME)/backend
# install -m 0644 $(INSTALLFLAGS) $(BE_PLUGIN) $(DESTDIR)$(libdir)/$(APPNAME)/backend
# Uncomment for installing netconf plugin (see NETCONF_SRC)
# install -d -m 0755 $(DESTDIR)$(libdir)/$(APPNAME)/netconf
# install -m 0644 $(INSTALLFLAGS) $(NETCONF_PLUGIN) $(DESTDIR)$(libdir)/$(APPNAME)/netconf
# Uncomment for installing restconf plugin (see RESTCONF_SRC). The conditional is
# because it is an option to disable restconf
#ifeq ($(with_restconf),yes)
# install -d -m 0755 $(DESTDIR)$(libdir)/$(APPNAME)/restconf
# install -m 0644 $(INSTALLFLAGS) $(RESTCONF_PLUGIN) $(DESTDIR)$(libdir)/$(APPNAME)/restconf
#endif
uninstall:
rm -rf $(DESTDIR)$(CLIXON_DEFAULT_CONFIG)
rm -rf $(DESTDIR)$(libdir)/$(APPNAME)
rm -rf $(DESTDIR)$(localstatedir)/$(APPNAME)
rm -rf $(DESTDIR)$(datarootdir)/$(APPNAME)
install-include:
depend:
$(CC) $(DEPENDFLAGS) $(INCLUDES) $(CFLAGS) -MM $(SRC) > .depend
#include .depend

View file

@ -1,111 +0,0 @@
# Clixon hello world example
* [Content](#content)
* [Compile and run](#compile)
* [Using the CLI](#using-the-cli)
* [Netconf](#netconf)
* [Restconf](#restconf)
* [Next steps](#next-steps)
## Content
This directory contains a Clixon example which includes a simple example. It contains the following files:
* `hello.xml` The configuration file. See [yang/clixon-config@<date>.yang](../../yang/clixon-config@2019-03-05.yang) for the documentation of all available fields.
* `clixon-hello@2019-04-17.yang` The yang spec of the example.
* `hello_cli.cli` CLIgen specification.
* `Makefile.in` Example makefile where plugins are built and installed
* `README.md` This file
## Compile and run
Before you start,
* Make [group setup](../../doc/FAQ.md#do-i-need-to-setup-anything-important)
```
make && sudo make install
```
Start backend in the background:
```
sudo clixon_backend
```
Start cli:
```
clixon_cli
```
## Using the CLI
The example CLI allows you to modify and view the data model using `set`, `delete` and `show` via generated code.
The following example shows how to add a very simple configuration `hello world` using the generated CLI. The config is added to the candidate database, shown, committed to running, and then deleted.
```
olof@vandal> clixon_cli
cli> set <?>
hello
cli> set hello world
cli> show configuration
hello world;
cli> commit
cli> delete <?>
all Delete whole candidate configuration
hello
cli> delete hello
cli> show configuration
cli> commit
cli> quit
olof@vandal>
```
## Netconf
Clixon also provides a Netconf interface. The following example starts a netconf client form the shell, adds the hello world config, commits it, and shows it:
```
olof@vandal> clixon_netconf -q
<rpc><edit-config><target><candidate/></target><config><hello xmlns="urn:example:hello"><world/></hello></config></edit-config></rpc>]]>]]>
<rpc-reply><ok/></rpc-reply>]]>]]>
<rpc><commit/></rpc>]]>]]>
<rpc-reply><ok/></rpc-reply>]]>]]>
<rpc><get-config><source><running/></source></get-config></rpc>]]>]]>
<rpc-reply><data><hello xmlns="urn:example:hello"><world/></hello></data></rpc-reply>]]>]]>
olof@vandal>
```
## Restconf
Clixon also provides a Restconf interface. A reverse proxy needs to be configured. There are [instructions how to setup Nginx](../../doc/FAQ.md#how-do-i-use-restconf) for Clixon.
Start restconf daemon
```
sudo su -c "/www-data/clixon_restconf" -s /bin/sh www-data &
```
Start sending restconf commands (using Curl):
```
olof@vandal> curl -X POST http://localhost/restconf/data -H "Content-Type: application/yang-data+json" -d '{"clixon-hello:hello":{"world":null}}'
olof@vandal> curl -X GET http://localhost/restconf/data
{
"data": {
"clixon-hello:hello": {
"world": null
}
}
}
```
## Next steps
The hello world example only has a Yang spec and a template CLI
spec. For more advanced applications, customized backend, cli, netconf
and restconf code callbacks becomes necessary.
Further, you may want to add upgrade, RPC:s, state data, notification
streams, authentication and authorization. The [main example](../main)
contains examples for such capabilities.
There are also [container examples](../../docker) and lots more.

View file

@ -1,14 +0,0 @@
module clixon-hello {
yang-version 1.1;
namespace "urn:example:hello";
prefix he;
revision 2019-04-17 {
description
"Clixon hello world example";
}
container hello{
container world{
presence true;
}
}
}

View file

@ -1,13 +0,0 @@
<clixon-config xmlns="http://clicon.org/config">
<CLICON_CONFIGFILE>/usr/local/etc/clixon.xml</CLICON_CONFIGFILE>
<CLICON_FEATURE>*:*</CLICON_FEATURE>
<CLICON_YANG_DIR>/usr/local/share/clixon</CLICON_YANG_DIR>
<CLICON_YANG_MODULE_MAIN>clixon-hello</CLICON_YANG_MODULE_MAIN>
<CLICON_CLI_MODE>hello</CLICON_CLI_MODE>
<CLICON_CLISPEC_DIR>/usr/local/lib/hello/clispec</CLICON_CLISPEC_DIR>
<CLICON_SOCK>/usr/local/var/hello.sock</CLICON_SOCK>
<CLICON_BACKEND_PIDFILE>/usr/local/var/hello.pidfile</CLICON_BACKEND_PIDFILE>
<CLICON_XMLDB_DIR>/usr/local/var/hello</CLICON_XMLDB_DIR>
<CLICON_STARTUP_MODE>init</CLICON_STARTUP_MODE>
<CLICON_MODULE_LIBRARY_RFC7895>false</CLICON_MODULE_LIBRARY_RFC7895>
</clixon-config>

View file

@ -1,54 +0,0 @@
# Common CLI syntax for both server and PMNode operatio mode
CLICON_MODE="hello";
CLICON_PROMPT="cli> ";
# Reference generated data model
set @datamodel, cli_set();
merge @datamodel, cli_merge();
create @datamodel, cli_create();
delete("Delete a configuration item") @datamodel, cli_del();
validate("Validate changes"), cli_validate();
commit("Commit the changes"), cli_commit();
quit("Quit"), cli_quit();
delete("Delete a configuration item") all("Delete whole candidate configuration"), delete_all("candidate");
startup("Store running as startup config"), db_copy("running", "startup");
no("Negate or remove") debug("Debugging parts of the system"), cli_debug_cli((int32)0);
debug("Debugging parts of the system"), cli_debug_cli((int32)1);{
level("Set debug level: 1..n") <level:int32>("Set debug level (0..n)"), cli_debug_backend();
}
discard("Discard edits (rollback 0)"), discard_changes();
compare("Compare running and candidate"), compare_dbs((int32)1);
show("Show a particular state of the system"){
xpath("Show configuration") <xpath:string>("XPATH expression") <ns:string>("Namespace"), show_conf_xpath("candidate");
version("Show version"), cli_show_version("candidate", "text", "/");
compare("Compare candidate and running databases"), compare_dbs((int32)0);{
xml("Show comparison in xml"), compare_dbs((int32)0);
text("Show comparison in text"), compare_dbs((int32)1);
}
configuration("Show configuration"), cli_show_config("candidate", "text", "/");{
xml("Show configuration as XML"), cli_show_config("candidate", "xml", "/");{
@datamodel, cli_show_auto("candidate", "xml");
}
cli("Show configuration as CLI commands"), cli_show_config("candidate", "cli", "/");{
@datamodel, cli_show_auto("candidate", "cli");
}
netconf("Show configuration as netconf edit-config operation"), cli_show_config("candidate", "netconf", "/");{
@datamodel, cli_show_auto("candidate", "netconf");
}
text("Show configuration as text"), cli_show_config("candidate","text","/");{
@datamodel, cli_show_auto("candidate", "text");
}
json("Show configuration as JSON"), cli_show_config("candidate", "json", "/");{
@datamodel, cli_show_auto("candidate", "json");
}
}
}
save("Save candidate configuration to XML file") <filename:string>("Filename (local filename)"), save_config_file("candidate","filename");
load("Load configuration from XML file") <filename:string>("Filename (local filename)"),load_config_file("filename", "replace");{
replace("Replace candidate with file contents"), load_config_file("filename", "replace");
merge("Merge file with existent candidate"), load_config_file("filename", "merge");
}

View file

@ -79,7 +79,8 @@ all: $(PLUGINS)
CLISPECS = $(APPNAME)_cli.cli
YANGSPECS = clixon-example@2019-11-05.yang
YANGSPECS = clixon-example@2019-11-05.yang # obsolete
YANGSPECS += clixon-example@2020-03-11.yang
# Backend plugin
BE_SRC = $(APPNAME)_backend.c

View file

@ -15,7 +15,7 @@
## Content
This directory contains a Clixon example used primarily for testing. It can be used as a basis for making new Clixon applications. But please consider also the minimal [hello](../hello) example as well. It contains the following files:
This directory contains a Clixon example used primarily as a part of the Clixon test suites. It can be used as a basis for making new Clixon applications. But please consider also the minimal [hello](../hello) example as well. It contains the following files:
* `example.xml` The configuration file. See [yang/clixon-config@<date>.yang](../../yang/clixon-config@2019-03-05.yang) for the documentation of all available fields.
* `clixon-example@2019-01-13.yang` The yang spec of the example.
* `example_cli.cli` CLIgen specification.

View file

@ -28,16 +28,13 @@ module clixon-example {
base if:interface-type;
}
/* Translation function example - See also example_cli */
container translate{
description "dont have lists directly under top since restconf cant address list directly";
list translate{
key k;
leaf k{
type string;
}
leaf value{
type string;
}
list translate{
key k;
leaf k{
type string;
}
leaf value{
type string;
}
}
/* State data (not config) for the example application*/

View file

@ -0,0 +1,185 @@
module clixon-example {
yang-version 1.1;
namespace "urn:example:clixon";
prefix ex;
description
"Clixon example used as a part of the Clixon test suite.
It can be used as a basis for making new Clixon applications.";
revision 2020-03-11 {
description "Added container around translation list. Released in Clixon 4.4.0";
}
revision 2019-11-05 {
description "Augment interface. Released in Clixon 4.3.0";
}
revision 2019-07-23 {
description "Extension e4. Released in Clixon 4.1.0";
}
revision 2019-01-13 {
description "Released in Clixon 3.9";
}
import ietf-interfaces {
prefix if;
}
import ietf-ip {
prefix ip;
}
import iana-if-type {
prefix ianaift;
}
/* Example interface type for tests, local callbacks, etc */
identity eth {
base if:interface-type;
}
identity loopback {
base if:interface-type;
}
/* Translation function example - See also example_cli */
container translate{
description "dont have lists directly under top since restconf cant address list directly";
list translate{
key k;
leaf k{
type string;
}
leaf value{
type string;
}
}
}
/* State data (not config) for the example application*/
container state {
config false;
description "state data for the example application (must be here for example get operation)";
leaf-list op {
type string;
}
}
augment "/if:interfaces/if:interface" {
container my-status {
config false;
description "For testing augment+state";
leaf int {
type int32;
}
leaf str {
type string;
}
}
}
/* yang extension implemented by the example backend code. */
extension e4 {
description
"The first child of the ex:e4 (unknown) statement is inserted into
the module as a regular data statement. This means that 'uses bar;'
in the ex:e4 statement below is a valid data node";
argument arg;
}
grouping bar {
leaf bar{
type string;
}
}
ex:e4 arg1{
uses bar;
}
/* Example notification as used in RFC 5277 and RFC 8040 */
notification event {
description "Example notification event.";
leaf event-class {
type string;
description "Event class identifier.";
}
container reportingEntity {
description "Event specific information.";
leaf card {
type string;
description "Line card identifier.";
}
}
leaf severity {
type string;
description "Event severity description.";
}
}
rpc client-rpc {
description "Example local client-side RPC that is processed by the
the netconf/restconf and not sent to the backend.
This is a clixon implementation detail: some rpc:s
are better processed by the client for API or perf reasons";
input {
leaf x {
type string;
}
}
output {
leaf x {
type string;
}
}
}
rpc empty {
description "Smallest possible RPC with no input or output sections";
}
rpc optional {
description "Small RPC with optional input and output";
input {
leaf x {
type string;
}
}
output {
leaf x {
type string;
}
}
}
rpc example {
description "Some example input/output for testing RFC7950 7.14.
RPC simply echoes the input for debugging.";
input {
leaf x {
description
"If a leaf in the input tree has a 'mandatory' statement with
the value 'true', the leaf MUST be present in an RPC invocation.";
type string;
mandatory true;
}
leaf y {
description
"If a leaf in the input tree has a 'mandatory' statement with the
value 'true', the leaf MUST be present in an RPC invocation.";
type string;
default "42";
}
leaf-list z {
description
"If a leaf-list in the input tree has one or more default
values, the server MUST use these values (XXX not supported)";
type string;
}
leaf w {
description
"If any node has a 'when' statement that would evaluate to
'false',then this node MUST NOT be present in the input tree.
(XXX not supported)";
type string;
when "/translate/k=5/value='w'";
}
}
output {
leaf x {
type string;
}
leaf y {
type string;
}
leaf z {
type string;
}
leaf w {
type string;
}
}
}
}

View file

@ -36,7 +36,7 @@ CLICON_PROMPT="%U@%H> ";
CLICON_PLUGIN="example_cli";
# Translate variable "value" by incrementing its characters
translate value (<value:string translate:incstr()>),cli_set("/translate/value");
translate value (<value:string translate:incstr()>),cli_set("/clixon-example:translate/translate=0/value");
# Note, when switching to PT, change datamodel to only @datamodel
set @datamodel, cli_set();

View file

@ -798,7 +798,7 @@ api_path2xpath_cvv(cvec *api_path,
* free(xpath)
* cvec_free(nsc);
* @endcode
*
* @see api_path2xml For api-path to xml translation (maybe could be combined?)
*/
int
api_path2xpath(char *api_path,
@ -1075,7 +1075,7 @@ api_path2xml_vec(char **vec,
* xbotp: <subid/>
* ybotp: Y_LEAF subid
* @note "api-path" is "URI-encoded path expression" definition in RFC8040 3.5.3
* @see api_path2xpath For api-path to xml xpath translation
* @see api_path2xpath For api-path to xpath translation (maybe could be combined?)
*/
int
api_path2xml(char *api_path,

View file

@ -41,8 +41,7 @@ datarootdir = @datarootdir@
# See also OPT_YANG_INSTALLDIR for the standard yang files
YANG_INSTALLDIR = @YANG_INSTALLDIR@
YANGSPECS = clixon-config@2019-09-11.yang
YANGSPECS += clixon-config@2020-02-22.yang
YANGSPECS = clixon-config@2020-02-22.yang
YANGSPECS += clixon-lib@2019-08-13.yang
YANGSPECS += clixon-rfc5277@2008-07-01.yang
YANGSPECS += clixon-xml-changelog@2019-03-21.yang