mirror of
https://github.com/kernelkit/infix.git
synced 2026-07-30 20:43:02 +02:00
klish-plugin-infix: extend copy command with curl
Allow copy from/to with either datastore, file (in /cfg), or curl. Signed-off-by: Joachim Wiberg <troglobit@gmail.com>
This commit is contained in:
@@ -3,8 +3,11 @@
|
||||
#include <errno.h>
|
||||
#include <stdio.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <termios.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/wait.h>
|
||||
|
||||
#include <sysrepo.h>
|
||||
#include <sysrepo_types.h>
|
||||
@@ -16,10 +19,136 @@
|
||||
#include <libyang/libyang.h>
|
||||
|
||||
#define ERRMSG "Error: "
|
||||
#define INFMSG "Note: "
|
||||
|
||||
#ifndef NELEMS
|
||||
#define NELEMS(v) (sizeof(v) / sizeof(v[0]))
|
||||
#endif
|
||||
|
||||
const uint8_t kplugin_infix_major = 1;
|
||||
const uint8_t kplugin_infix_minor = 0;
|
||||
|
||||
struct infix_ds {
|
||||
char *name; /* startup-config, etc. */
|
||||
char *sysrepocfg; /* ds name in sysrepocfg */
|
||||
int datastore; /* sr_datastore_t and -1 */
|
||||
int rw; /* read-write:1 or not:0 */
|
||||
char *path; /* local path or NULL */
|
||||
};
|
||||
|
||||
struct infix_ds infix_config[] = {
|
||||
{ "startup-config", "startup", SR_DS_STARTUP, 1, "/cfg/startup-config.cfg" },
|
||||
{ "running-config", "running", SR_DS_RUNNING, 1, NULL },
|
||||
{ "candidate-config", "candidate", SR_DS_CANDIDATE, 1, NULL },
|
||||
{ "operational-config", "operational", SR_DS_OPERATIONAL, 1, NULL },
|
||||
{ "factory-config", NULL, SR_DS_FACTORY_DEFAULT, 0, "/cfg/factory-config.cfg" },
|
||||
};
|
||||
|
||||
static int has_ext(const char *fn, const char *ext)
|
||||
{
|
||||
size_t pos = strlen(fn);
|
||||
size_t len = strlen(ext);
|
||||
|
||||
if (len < pos && !strcmp(&fn[pos - len], ext))
|
||||
return pos - len;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static char *cfg_adjust(const char *fn, char *buf, size_t len)
|
||||
{
|
||||
if (strncmp(fn, "/cfg/", 5)) {
|
||||
if (fn[0] == '.' || fn[0] == '/')
|
||||
return NULL;
|
||||
|
||||
snprintf(buf, len, "/cfg/%s", fn);
|
||||
}
|
||||
|
||||
if (!has_ext(buf, ".cfg"))
|
||||
strcat(buf, ".cfg");
|
||||
|
||||
return buf;
|
||||
}
|
||||
|
||||
static char rawgetch(void)
|
||||
{
|
||||
struct termios saved, c;
|
||||
char key;
|
||||
|
||||
if (tcgetattr(fileno(stdin), &saved) < 0)
|
||||
return -1;
|
||||
|
||||
c = saved;
|
||||
c.c_lflag &= ~ICANON;
|
||||
c.c_lflag &= ~ECHO;
|
||||
c.c_cc[VMIN] = 1;
|
||||
c.c_cc[VTIME] = 0;
|
||||
|
||||
if (tcsetattr(fileno(stdin), TCSANOW, &c) < 0) {
|
||||
tcsetattr(fileno(stdin), TCSANOW, &saved);
|
||||
return -1;
|
||||
}
|
||||
|
||||
key = getchar();
|
||||
tcsetattr(fileno(stdin), TCSANOW, &saved);
|
||||
|
||||
return key;
|
||||
}
|
||||
|
||||
static int yorn(const char *fmt, ...)
|
||||
{
|
||||
va_list ap;
|
||||
char ch;
|
||||
|
||||
va_start(ap, fmt);
|
||||
vfprintf(stderr, fmt, ap);
|
||||
va_end(ap);
|
||||
|
||||
fprintf(stderr, " (y/N)? ");
|
||||
ch = rawgetch();
|
||||
fprintf(stderr, "%c\n", ch);
|
||||
if (ch != 'y' && ch != 'Y')
|
||||
return 0;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int systemf(const char *fmt, ...)
|
||||
{
|
||||
va_list ap;
|
||||
char *cmd;
|
||||
int len;
|
||||
int rc;
|
||||
|
||||
va_start(ap, fmt);
|
||||
len = vsnprintf(NULL, 0, fmt, ap);
|
||||
va_end(ap);
|
||||
|
||||
cmd = alloca(++len);
|
||||
if (!cmd) {
|
||||
errno = ENOMEM;
|
||||
return -1;
|
||||
}
|
||||
|
||||
va_start(ap, fmt);
|
||||
vsnprintf(cmd, len, fmt, ap);
|
||||
va_end(ap);
|
||||
|
||||
// fprintf(stderr, INFMSG "CMD: '%s'\n", cmd);
|
||||
|
||||
rc = system(cmd);
|
||||
if (rc == -1)
|
||||
return -1;
|
||||
|
||||
if (WIFEXITED(rc)) {
|
||||
errno = 0;
|
||||
rc = WEXITSTATUS(rc);
|
||||
} else if (WIFSIGNALED(rc)) {
|
||||
errno = EINTR;
|
||||
rc = -1;
|
||||
}
|
||||
|
||||
return rc;
|
||||
}
|
||||
|
||||
/*
|
||||
* Print sysrepo session errors followed by an optional string.
|
||||
@@ -51,18 +180,11 @@ end:
|
||||
}
|
||||
}
|
||||
|
||||
int infix_files(kcontext_t *ctx)
|
||||
static int files(const char *path, const char *stripext)
|
||||
{
|
||||
const char *path;
|
||||
struct dirent *d;
|
||||
DIR *dir;
|
||||
|
||||
path = kcontext_script(ctx);
|
||||
if (!path) {
|
||||
fprintf(stderr, ERRMSG "missing path argument to file search.\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
dir = opendir(path);
|
||||
if (!dir) {
|
||||
fprintf(stderr, ERRMSG "%s", strerror(errno));
|
||||
@@ -70,16 +192,99 @@ int infix_files(kcontext_t *ctx)
|
||||
}
|
||||
|
||||
while ((d = readdir(dir))) {
|
||||
char name[sizeof(d->d_name) + 1];
|
||||
|
||||
if (d->d_type != DT_REG)
|
||||
continue;
|
||||
|
||||
printf("%s\n", d->d_name);
|
||||
strncpy(name, d->d_name, sizeof(name));
|
||||
if (stripext) {
|
||||
size_t pos = has_ext(name, stripext);
|
||||
|
||||
if (pos)
|
||||
name[pos] = 0;
|
||||
}
|
||||
|
||||
printf("%s\n", name);
|
||||
}
|
||||
|
||||
return closedir(dir);
|
||||
}
|
||||
|
||||
int infix_datastore(kcontext_t *ctx)
|
||||
{
|
||||
const char *ds;
|
||||
|
||||
ds = kcontext_script(ctx);
|
||||
if (!ds)
|
||||
goto done;
|
||||
|
||||
if (!strcmp(ds, "src")) {
|
||||
puts("factory-config");
|
||||
puts("running-config");
|
||||
}
|
||||
if (!strcmp(ds, "dst")) {
|
||||
puts("running-config");
|
||||
}
|
||||
|
||||
done:
|
||||
return files("/cfg", ".cfg");
|
||||
}
|
||||
|
||||
int infix_dir(kcontext_t *ctx)
|
||||
{
|
||||
return systemf("ls --color=always --group-directories-first -A /cfg");
|
||||
}
|
||||
|
||||
int infix_erase(kcontext_t *ctx)
|
||||
{
|
||||
kpargv_t *pargv = kcontext_pargv(ctx);
|
||||
const char *path;
|
||||
char *fn;
|
||||
|
||||
path = kparg_value(kpargv_find(pargv, "file"));
|
||||
if (!path) {
|
||||
fprintf(stderr, ERRMSG "missing file argument to remove.\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (access(path, F_OK)) {
|
||||
size_t len = strlen(path) + 10;
|
||||
|
||||
fn = alloca(len);
|
||||
if (!fn) {
|
||||
fprintf(stderr, ERRMSG "failed allocating memory.\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
cfg_adjust(path, fn, len);
|
||||
} else
|
||||
fn = (char *)path;
|
||||
|
||||
if (!yorn("Remove %s, are you sure", fn))
|
||||
return 0;
|
||||
|
||||
if (remove(fn)) {
|
||||
fprintf(stderr, ERRMSG "failed removing %s: %s\n", fn, strerror(errno));
|
||||
return -1;
|
||||
}
|
||||
closedir(dir);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int infix_files(kcontext_t *ctx)
|
||||
{
|
||||
const char *path;
|
||||
|
||||
path = kcontext_script(ctx);
|
||||
if (!path) {
|
||||
fprintf(stderr, ERRMSG "missing path argument to file search.\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
return files(path, NULL);
|
||||
}
|
||||
|
||||
int infix_ifaces(kcontext_t *ctx)
|
||||
{
|
||||
(void)ctx;
|
||||
@@ -87,91 +292,173 @@ int infix_ifaces(kcontext_t *ctx)
|
||||
return 0;
|
||||
}
|
||||
|
||||
int infix_ds_from_str(const char *text, sr_datastore_t *ds)
|
||||
static const char *infix_ds(const char *text, const char *type, struct infix_ds **ds)
|
||||
{
|
||||
size_t len = strlen(text);
|
||||
size_t i, len = strlen(text);
|
||||
|
||||
if (!strncmp("startup-config", text, len))
|
||||
*ds = SR_DS_STARTUP;
|
||||
else if (!strncmp("running-config", text, len))
|
||||
*ds = SR_DS_RUNNING;
|
||||
else if (!strncmp("candidate-config", text, len))
|
||||
*ds = SR_DS_CANDIDATE;
|
||||
else if (!strncmp("operational-config", text, len))
|
||||
*ds = SR_DS_OPERATIONAL;
|
||||
else if (!strncmp("factory-config", text, len))
|
||||
*ds = SR_DS_FACTORY_DEFAULT;
|
||||
else
|
||||
return -1;
|
||||
for (i = 0; i < NELEMS(infix_config); i++) {
|
||||
if (!strncmp(infix_config[i].name, text, len)) {
|
||||
*ds = &infix_config[i];
|
||||
return infix_config[i].name;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
if (strchr(text, '/') && !strstr(text, "://")) {
|
||||
fprintf(stderr, ERRMSG "invalid %s argument: %s\n", type, text);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return text;
|
||||
}
|
||||
|
||||
int infix_copy(kcontext_t *ctx)
|
||||
{
|
||||
struct infix_ds *srcds = NULL, *dstds = NULL;
|
||||
kpargv_t *pargv = kcontext_pargv(ctx);
|
||||
sr_datastore_t srcds, dstds;
|
||||
kparg_t *srcarg, *dstarg;
|
||||
sr_session_ctx_t *sess;
|
||||
const char *src, *dst;
|
||||
sr_conn_ctx_t *conn;
|
||||
int err;
|
||||
int rc = 0;
|
||||
|
||||
srcarg = kpargv_find(pargv, "src");
|
||||
dstarg = kpargv_find(pargv, "dst");
|
||||
if (!srcarg || !dstarg)
|
||||
src = kparg_value(kpargv_find(pargv, "src"));
|
||||
dst = kparg_value(kpargv_find(pargv, "dst"));
|
||||
if (!src || !dst)
|
||||
goto err;
|
||||
|
||||
if (infix_ds_from_str(kparg_value(srcarg), &srcds)) {
|
||||
fprintf(stderr, ERRMSG "\"%s\" is not the name of any known datastore\n", kparg_value(srcarg));
|
||||
src = infix_ds(src, "source", &srcds);
|
||||
if (!src)
|
||||
goto err;
|
||||
}
|
||||
if (infix_ds_from_str(kparg_value(dstarg), &dstds)) {
|
||||
fprintf(stderr, ERRMSG "\"%s\" is not the name of any known datastore\n", kparg_value(dstarg));
|
||||
dst = infix_ds(dst, "destination", &dstds);
|
||||
if (!dst)
|
||||
goto err;
|
||||
|
||||
if (!strcmp(src, dst)) {
|
||||
fprintf(stderr, ERRMSG "source and destination are the same, aborting.");
|
||||
goto err;
|
||||
}
|
||||
|
||||
switch (srcds) {
|
||||
case SR_DS_STARTUP:
|
||||
case SR_DS_RUNNING:
|
||||
case SR_DS_FACTORY_DEFAULT:
|
||||
break;
|
||||
default:
|
||||
fprintf(stderr,
|
||||
ERRMSG "\"%s\" is not a valid source datastore\n", kparg_value(srcarg));
|
||||
goto err;
|
||||
/* 1. Regular ds copy */
|
||||
if (srcds && dstds) {
|
||||
/* Ensure the dst ds is writable */
|
||||
if (!dstds->rw) {
|
||||
invalid:
|
||||
fprintf(stderr, ERRMSG "\"%s\" is not a valid file or datastore\n", dst);
|
||||
goto err;
|
||||
}
|
||||
|
||||
if (sr_connect(SR_CONN_DEFAULT, &conn)) {
|
||||
fprintf(stderr, ERRMSG "connection to datastore failed\n");
|
||||
goto err;
|
||||
}
|
||||
if (sr_session_start(conn, dstds->datastore, &sess)) {
|
||||
fprintf(stderr, ERRMSG "unable to open transaction to %s\n", dst);
|
||||
} else {
|
||||
rc = sr_copy_config(sess, NULL, srcds->datastore, 0);
|
||||
if (rc)
|
||||
emsg(sess, ERRMSG "unable to copy configuration (%d)\n", rc);
|
||||
}
|
||||
rc = sr_disconnect(conn);
|
||||
} else {
|
||||
char temp_file[20] = "/tmp/copy.XXXXXX";
|
||||
const char *tmpfn = NULL;
|
||||
const char *fn = NULL;
|
||||
char adjust[256];
|
||||
|
||||
if (srcds) {
|
||||
/* 2. Export from a datastore somewhere else */
|
||||
if (strstr(dst, "://")) {
|
||||
snprintf(adjust, sizeof(adjust), "/tmp/%s.cfg", srcds->name);
|
||||
tmpfn = adjust;
|
||||
fn = tmpfn;
|
||||
} else {
|
||||
fn = cfg_adjust(dst, adjust, sizeof(adjust));
|
||||
if (!fn) {
|
||||
fprintf(stderr, "2.3\n");
|
||||
goto invalid;
|
||||
}
|
||||
|
||||
if (!access(fn, F_OK) && !yorn("Overwrite existing file %s", fn)) {
|
||||
fprintf(stderr, "OK, aborting.\n");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
if (srcds->path)
|
||||
fn = srcds->path; /* user directly => correct name */
|
||||
else
|
||||
rc = systemf("sysrepocfg -d %s -X%s -f json", srcds->sysrepocfg, fn);
|
||||
|
||||
if (rc)
|
||||
fprintf(stderr, ERRMSG "failed exporting %s\n", src);
|
||||
else if (tmpfn) {
|
||||
rc = systemf("curl -T %s %s", fn, dst);
|
||||
if (rc)
|
||||
fprintf(stderr, ERRMSG "failed uploading %s to %s", src, dst);
|
||||
}
|
||||
} else if (dstds) {
|
||||
if (!dstds->sysrepocfg)
|
||||
goto invalid;
|
||||
if (!dstds->rw) {
|
||||
fprintf(stderr, ERRMSG "not possible to write to %s", dst);
|
||||
goto err;
|
||||
}
|
||||
|
||||
/* 3. Import from somewhere to a datastore */
|
||||
if (strstr(src, "://")) {
|
||||
tmpfn = mktemp(temp_file);
|
||||
fn = tmpfn;
|
||||
} else {
|
||||
fn = cfg_adjust(src, adjust, sizeof(adjust));
|
||||
if (!fn)
|
||||
goto invalid;
|
||||
}
|
||||
|
||||
if (tmpfn)
|
||||
rc = systemf("curl -o %s %s", fn, src);
|
||||
if (rc) {
|
||||
fprintf(stderr, ERRMSG "failed downloading %s", src);
|
||||
} else {
|
||||
rc = systemf("sysrepocfg -d %s -I%s -f json", dstds->sysrepocfg, fn);
|
||||
if (rc)
|
||||
fprintf(stderr, ERRMSG "failed loading %s from %s", dst, src);
|
||||
}
|
||||
} else {
|
||||
if (strstr(src, "://") && strstr(dst, "://")) {
|
||||
fprintf(stderr, ERRMSG "copy from remote to remote is not supported.\n");
|
||||
goto err;
|
||||
}
|
||||
|
||||
if (strstr(src, "://")) {
|
||||
fn = cfg_adjust(dst, adjust, sizeof(adjust));
|
||||
if (!fn)
|
||||
goto invalid;
|
||||
|
||||
if (!access(fn, F_OK)) {
|
||||
if (!yorn("Overwrite existing file %s", fn)) {
|
||||
fprintf(stderr, "OK, aborting.\n");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
rc = systemf("curl -o %s %s", fn, src);
|
||||
} else if (strstr(dst, "://")) {
|
||||
fn = cfg_adjust(src, adjust, sizeof(adjust));
|
||||
if (!fn)
|
||||
goto invalid;
|
||||
|
||||
if (access(fn, F_OK))
|
||||
fprintf(stderr, ERRMSG "no such file %s, aborting.", fn);
|
||||
else
|
||||
rc = systemf("curl -T %s %s", fn, dst);
|
||||
}
|
||||
}
|
||||
|
||||
if (tmpfn)
|
||||
rc = remove(tmpfn);
|
||||
}
|
||||
|
||||
switch (dstds) {
|
||||
case SR_DS_STARTUP:
|
||||
case SR_DS_RUNNING:
|
||||
break;
|
||||
default:
|
||||
fprintf(stderr, ERRMSG "\"%s\" is not a valid destination datastore\n", kparg_value(dstarg));
|
||||
goto err;
|
||||
}
|
||||
|
||||
if (sr_connect(SR_CONN_DEFAULT, &conn)) {
|
||||
fprintf(stderr, ERRMSG "connection to datastore failed\n");
|
||||
goto err;
|
||||
}
|
||||
if (sr_session_start(conn, dstds, &sess)) {
|
||||
fprintf(stderr, ERRMSG "unable to open transaction to %s\n", kparg_value(dstarg));
|
||||
goto err_disconnect;
|
||||
}
|
||||
|
||||
err = sr_copy_config(sess, NULL, srcds, 0);
|
||||
if (err) {
|
||||
emsg(sess, ERRMSG "unable to copy configuration (%d)\n", err);
|
||||
goto err_disconnect;
|
||||
}
|
||||
|
||||
sr_disconnect(conn);
|
||||
return 0;
|
||||
|
||||
err_disconnect:
|
||||
sr_disconnect(conn);
|
||||
err:
|
||||
return -1;
|
||||
return rc;
|
||||
}
|
||||
|
||||
int infix_commit(kcontext_t *ctx)
|
||||
@@ -290,6 +577,9 @@ int kplugin_infix_init(kcontext_t *ctx)
|
||||
|
||||
kplugin_add_syms(plugin, ksym_new("copy", infix_copy));
|
||||
kplugin_add_syms(plugin, ksym_new("commit", infix_commit));
|
||||
kplugin_add_syms(plugin, ksym_new("datastore", infix_datastore));
|
||||
kplugin_add_syms(plugin, ksym_new("dir", infix_dir));
|
||||
kplugin_add_syms(plugin, ksym_new("erase", infix_erase));
|
||||
kplugin_add_syms(plugin, ksym_new("files", infix_files));
|
||||
kplugin_add_syms(plugin, ksym_new("ifaces", infix_ifaces));
|
||||
kplugin_add_syms(plugin, ksym_new("rpc", infix_rpc));
|
||||
|
||||
@@ -21,19 +21,23 @@
|
||||
Oneliners = n
|
||||
</PLUGIN>
|
||||
|
||||
<PTYPE name="CFG">
|
||||
<COMPL>
|
||||
<ACTION sym="datastore@infix">cfg</ACTION>
|
||||
</COMPL>
|
||||
<ACTION sym="STRING"/>
|
||||
</PTYPE>
|
||||
|
||||
<PTYPE name="DATASTORE">
|
||||
<COMPL>
|
||||
<ACTION sym="printl">factory-config</ACTION>
|
||||
<ACTION sym="printl">startup-config</ACTION>
|
||||
<ACTION sym="printl">running-config</ACTION>
|
||||
<ACTION sym="datastore@infix">src</ACTION>
|
||||
</COMPL>
|
||||
<ACTION sym="STRING"/>
|
||||
</PTYPE>
|
||||
|
||||
<PTYPE name="RW_DATASTORE">
|
||||
<COMPL>
|
||||
<ACTION sym="printl">startup-config</ACTION>
|
||||
<ACTION sym="printl">running-config</ACTION>
|
||||
<ACTION sym="datastore@infix">dst</ACTION>
|
||||
</COMPL>
|
||||
<ACTION sym="STRING"/>
|
||||
</PTYPE>
|
||||
@@ -95,6 +99,13 @@
|
||||
<ACTION sym="STRING"/>
|
||||
</PTYPE>
|
||||
|
||||
<PTYPE name="FILES">
|
||||
<COMPL>
|
||||
<ACTION sym="files@infix">/cfg</ACTION>
|
||||
</COMPL>
|
||||
<ACTION sym="STRING"/>
|
||||
</PTYPE>
|
||||
|
||||
<PTYPE name="IFACES">
|
||||
<COMPL>
|
||||
<ACTION sym="ifaces@infix"/>
|
||||
@@ -154,7 +165,16 @@
|
||||
<COMMAND name="copy" help="Copy configuration, e.g., copy running-config startup-config">
|
||||
<PARAM name="src" ptype="/DATASTORE" help="Source datastore"/>
|
||||
<PARAM name="dst" ptype="/RW_DATASTORE" help="Destination datastore"/>
|
||||
<ACTION sym="copy@infix"/>
|
||||
<ACTION sym="copy@infix" in="tty" out="tty" interrupt="true"/>
|
||||
</COMMAND>
|
||||
|
||||
<COMMAND name="dir" help="List available configuration files">
|
||||
<ACTION sym="dir@infix"/>
|
||||
</COMMAND>
|
||||
|
||||
<COMMAND name="erase" help="Erase a configuration file">
|
||||
<PARAM name="file" ptype="/FILES" help="Configuration file"/>
|
||||
<ACTION sym="erase@infix" in="tty" out="tty" interrupt="true"/>
|
||||
</COMMAND>
|
||||
|
||||
<COMMAND name="password" help="Password tools" mode="switch">
|
||||
|
||||
Reference in New Issue
Block a user