Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion configure
Original file line number Diff line number Diff line change
Expand Up @@ -4939,7 +4939,7 @@ if test "x$with_systemdsystemunitdir" != "xno" ; then
HAVE_SYSTEMD=true
fi

ac_config_files="$ac_config_files Makefile clixon-controller.service src/Makefile src/controller.xml yang/Makefile util/Makefile docker/Makefile test/config.sh doc/Makefile extensions/Makefile extensions/plugins/Makefile extensions/plugins/junos-native/Makefile extensions/yang/Makefile extensions/yang/junos-bgp/Makefile extensions/yang/junos-macsec/Makefile extensions/yang/junos-users/Makefile"
ac_config_files="$ac_config_files Makefile clixon-controller.service src/Makefile src/controller.xml yang/Makefile util/Makefile docker/Makefile test/config.sh doc/Makefile extensions/Makefile extensions/plugins/Makefile extensions/plugins/junos-native/Makefile extensions/plugins/cli-command/Makefile extensions/yang/Makefile extensions/yang/junos-bgp/Makefile extensions/yang/junos-macsec/Makefile extensions/yang/junos-users/Makefile"

cat >confcache <<\_ACEOF
# This file is a shell script that caches the results of configure
Expand Down Expand Up @@ -5654,6 +5654,7 @@ do
"extensions/Makefile") CONFIG_FILES="$CONFIG_FILES extensions/Makefile" ;;
"extensions/plugins/Makefile") CONFIG_FILES="$CONFIG_FILES extensions/plugins/Makefile" ;;
"extensions/plugins/junos-native/Makefile") CONFIG_FILES="$CONFIG_FILES extensions/plugins/junos-native/Makefile" ;;
"extensions/plugins/cli-command/Makefile") CONFIG_FILES="$CONFIG_FILES extensions/plugins/cli-command/Makefile" ;;
"extensions/yang/Makefile") CONFIG_FILES="$CONFIG_FILES extensions/yang/Makefile" ;;
"extensions/yang/junos-bgp/Makefile") CONFIG_FILES="$CONFIG_FILES extensions/yang/junos-bgp/Makefile" ;;
"extensions/yang/junos-macsec/Makefile") CONFIG_FILES="$CONFIG_FILES extensions/yang/junos-macsec/Makefile" ;;
Expand Down
1 change: 1 addition & 0 deletions configure.ac
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,7 @@ AC_CONFIG_FILES([Makefile
extensions/Makefile
extensions/plugins/Makefile
extensions/plugins/junos-native/Makefile
extensions/plugins/cli-command/Makefile
extensions/yang/Makefile
extensions/yang/junos-bgp/Makefile
extensions/yang/junos-macsec/Makefile
Expand Down
2 changes: 1 addition & 1 deletion extensions/plugins/Makefile.in
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ VPATH = @srcdir@
srcdir = @srcdir@
top_srcdir = @top_srcdir@

SUBDIRS = junos-native
SUBDIRS = junos-native cli-command

.PHONY: all clean depend install $(SUBDIRS)

Expand Down
6 changes: 5 additions & 1 deletion extensions/plugins/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,11 @@ The following plugins exist:
Juniper PTX,MX and QFX (possibly others). The plugin rewrites
the XML config on pull/sync and push/commit

To build and install a plugin, for example junos_native, do
- cli-command CLI plugin to run arbitrary shell commands on the server side.
The plugin maps CLI commands to shell commands and returns
the output as CLI output.

To build and install a plugin, for example junos_native, do:

cd junos-native
make
Expand Down
67 changes: 67 additions & 0 deletions extensions/plugins/cli-command/Makefile.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
#
# ***** BEGIN LICENSE BLOCK *****
#
# Copyright (C) 2025 Olof Hagsand
#
# 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.
#
# ***** END LICENSE BLOCK *****
#

srcdir = .
top_srcdir = ../../..
top_builddir = ../../..
prefix = /usr/local
exec_prefix = /usr/local
bindir = ${exec_prefix}/bin
includedir = ${prefix}/include
datarootdir = ${prefix}/share
sysconfdir = ${prefix}/etc
localstatedir = ${prefix}/var
runstatedir = ${localstatedir}/run
libdir = ${exec_prefix}/lib
version = 1.6.0-1+5+g966282a

APPNAME = controller

CC = gcc
CFLAGS = -O2 -Wall -Werror -fPIC
LDFLAGS =
INSTALLFLAGS = -s

INCLUDES = -I. -I$(top_srcdir)/src
CPPFLAGS = @CPPFLAGS@ -fPIC -DCONTROLLER_VERSION=\"$(version)\"

CLI_PLUGIN = $(APPNAME)_cli_command.so
CLI_SRC = $(APPNAME)_cli_command.c
CLI_OBJ = $(CLI_SRC:%.c=%.o)

$(CLI_PLUGIN): $(CLI_OBJ) $(GENOBJS)
$(CC) -Wall -shared $(LDFLAGS) -o $@ -lc $^ -lclixon -lclixon_cli

PLUGINS = $(CLI_PLUGIN)
OBJS = $(CLI_OBJ)

all: $(PLUGINS)

clean:
rm -f $(PLUGINS) $(OBJS)

install: $(PLUGINS)
install -d -m 0755 $(DESTDIR)$(libdir)/$(APPNAME)/cli
install -m 0644 $(INSTALLFLAGS) $(CLI_PLUGIN) $(DESTDIR)$(libdir)/$(APPNAME)/cli

depend:
$(CC) $(DEPENDFLAGS) $(INCLUDES) $(CFLAGS) -MM $(SRC) > .depend
18 changes: 18 additions & 0 deletions extensions/plugins/cli-command/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# CLI command plugin

The CLI command plugin allows mapping of CLI commands to arbitrary shell commands on the server side.
To build and install the CLI command plugin, follow these steps:

```bash
cd cli-command
make
make install
```

You have to create a CLI specification file that maps CLI commands to shell commands.
An example specification named example.cli is provided in the cli-command directory.
The CLI specification file should be installed in the Clixon CLI specification directory:

```bash
cp example.cli /usr/local/lib/controller/clispec/
```
168 changes: 168 additions & 0 deletions extensions/plugins/cli-command/controller_cli_command.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
#include <errno.h>
#include <libgen.h>
#include <pwd.h>
#include <sys/wait.h>
#include <unistd.h>

#include <cligen/cligen.h>
#include <clixon/clixon.h>

/*! Generic cli command to run script with arguments
*
* @param[in] h Clixon handle
* @param[in] cvv Vector of command variables
* @param[in] argv Vector of arguments, first is script runner, second is script path
* @retval 0 OK
* @retval -1 Error
*/
int cli_command_run(clixon_handle h, cvec *cvv, cvec *argv)
{
int pid = 0;
int retval = -1;
int s = 0;
int arg_count = 0;
int cvv_count = 0;
int i = 0;
int status = 0;
char *script_path = NULL;
char *runner = NULL;
char *buf = NULL;
char *work_dir = NULL;
char *reserve_path = NULL;
char **args = NULL;
size_t bufsize = 0;
struct passwd pw, *pwresult = NULL;


/* Check parameters */
if (cvec_len(argv) == 0) {
clixon_err(OE_PLUGIN, EINVAL, "Can not find argument");
goto done;
}

/* get data */
arg_count = cvec_len(argv);
cvv_count = cvec_len(cvv);

runner = cv_string_get(cvec_i(argv, 0));

if (arg_count > 1) {
script_path = cv_string_get(cvec_i(argv, 1));
}

if (script_path) {
reserve_path = strdup(script_path);
work_dir = dirname(reserve_path);
}

bufsize = sysconf(_SC_GETPW_R_SIZE_MAX);

if (bufsize == -1) {
bufsize = 16384;
}

buf = malloc(bufsize);

if (buf == NULL) {
perror("malloc");
goto done;
}

s = getpwuid_r(getuid(), &pw, buf, bufsize, &pwresult);

if (pwresult == NULL) {
if (s == 0)
clixon_err(OE_PLUGIN, errno, "getpwuid_r");
else
perror("getpwuid_r");
goto done;
}

/* Prepare arguments for execlp */
args = malloc((arg_count + cvv_count) * sizeof(char *));

if (args == NULL) {
perror("malloc");
goto done;
}

for (i = 0; i < arg_count; i++) {
args[i] = cv_string_get(cvec_i(argv, i));
}

for (i = 0; i < cvv_count; i++) {
args[arg_count + i] = cv_string_get(cvec_i(cvv, i + 1));
}

/* main run */
if ((pid = fork()) == 0) {

/* child process */
if ((work_dir ? chdir(work_dir) : chdir(pw.pw_dir)) < 0) {
clixon_err(OE_PLUGIN, errno, "chdir");
}

execvp(runner, args);
clixon_err(OE_PLUGIN, errno, "Error running script");

goto done;
} else if(pid == -1) {
clixon_err(OE_PLUGIN, errno, "fork");
} else {
/* parent process */
if (waitpid(pid, &status, 0) != pid ){
clixon_err(OE_PLUGIN, errno, "waitpid error");
goto done;
} else {
retval = WEXITSTATUS(status);
goto done;
}
}

done:
if (buf)
free(buf);
if (reserve_path)
free(reserve_path);
if (args)
free(args);
return retval;
}

/*! Called when plugin is loaded.
* @param[in] h Clixon handle
* @retval 0 OK
* @retval -1 Error
*/
int controller_cli_start(clixon_handle h)
{
return 0;
}

/*! Called just before plugin unloaded.
*
* @param[in] h Clixon handle
* @retval 0 OK
* @retval -1 Error
*/
int controller_cli_exit(clixon_handle h)
{
return 0;
}

static clixon_plugin_api api = {
"controller_test",
clixon_plugin_init,
controller_cli_start,
controller_cli_exit,
};

/*! CLI plugin initialization
*
* @param[in] h Clixon handle
* @retval api Pointer to API struct
*/
clixon_plugin_api *clixon_plugin_init(clixon_handle h)
{
return &api;
}
6 changes: 6 additions & 0 deletions extensions/plugins/cli-command/example.cli
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
CLICON_MODE="operation";
CLICON_PLUGIN="controller_cli_command";

test("Test") {
foo("Test"), cli_command_run("/home/debian/test.py", "foo", "bar");
}