Commit 3892b839 authored by Jiří Klimeš's avatar Jiří Klimeš

cli: add 'nmcli connection export' (rh #1034105)

Synopsis:
  nmcli connection export [ id | uuid | path] <ID> [<output file>]

for exporting VPN connections.

https://bugzilla.redhat.com/show_bug.cgi?id=1034105
parent 9a0e4bae
...@@ -48,6 +48,7 @@ ...@@ -48,6 +48,7 @@
#define PROMPT_VPN_TYPE _("VPN type: ") #define PROMPT_VPN_TYPE _("VPN type: ")
#define PROMPT_MASTER _("Master: ") #define PROMPT_MASTER _("Master: ")
#define PROMPT_CONNECTION _("Connection (name, UUID, or path): ") #define PROMPT_CONNECTION _("Connection (name, UUID, or path): ")
#define PROMPT_VPN_CONNECTION _("VPN connection (name, UUID, or path): ")
#define PROMPT_CONNECTIONS _("Connection(s) (name, UUID, or path): ") #define PROMPT_CONNECTIONS _("Connection(s) (name, UUID, or path): ")
#define PROMPT_ACTIVE_CONNECTIONS _("Connection(s) (name, UUID, path or apath): ") #define PROMPT_ACTIVE_CONNECTIONS _("Connection(s) (name, UUID, path or apath): ")
...@@ -276,7 +277,8 @@ usage (void) ...@@ -276,7 +277,8 @@ usage (void)
" monitor [id | uuid | path] <ID> ...\n\n" " monitor [id | uuid | path] <ID> ...\n\n"
" reload\n\n" " reload\n\n"
" load <filename> [ <filename>... ]\n\n" " load <filename> [ <filename>... ]\n\n"
" import [--temporary] type <type> file <file to import>\n\n")); " import [--temporary] type <type> file <file to import>\n\n"
" export [id | uuid | path] <ID> [<output file>]\n\n"));
} }
static void static void
...@@ -537,6 +539,17 @@ usage_connection_import (void) ...@@ -537,6 +539,17 @@ usage_connection_import (void)
"is imported by NetworkManager VPN plugins.\n\n")); "is imported by NetworkManager VPN plugins.\n\n"));
} }
static void
usage_connection_export (void)
{
g_printerr (_("Usage: nmcli connection export { ARGUMENTS | help }\n"
"\n"
"ARGUMENTS := [id | uuid | path] <ID> [<output file>]\n"
"\n"
"Export a connection. Only VPN connections are supported at the moment.\n"
"The data are directed to standard output or to a file if a name is given.\n\n"));
}
static gboolean static gboolean
usage_connection_second_level (const char *cmd) usage_connection_second_level (const char *cmd)
{ {
...@@ -566,6 +579,8 @@ usage_connection_second_level (const char *cmd) ...@@ -566,6 +579,8 @@ usage_connection_second_level (const char *cmd)
usage_connection_load (); usage_connection_load ();
else if (matches (cmd, "import") == 0) else if (matches (cmd, "import") == 0)
usage_connection_import (); usage_connection_import ();
else if (matches (cmd, "export") == 0)
usage_connection_export ();
else else
ret = FALSE; ret = FALSE;
return ret; return ret;
...@@ -6885,33 +6900,59 @@ gen_compat_devices (const char *text, int state) ...@@ -6885,33 +6900,59 @@ gen_compat_devices (const char *text, int state)
return ret; return ret;
} }
static char * static const char **
gen_vpn_uuids (const char *text, int state) _create_vpn_array (const GPtrArray *connections, gboolean uuid)
{ {
const GPtrArray *connections = nmc_tab_completion.nmc->connections; int c, idx = 0;
int c, u = 0; const char **array;
const char **uuids;
char *ret;
if (connections->len < 1) if (connections->len < 1)
return NULL; return NULL;
uuids = g_new (const char *, connections->len + 1); array = g_new (const char *, connections->len + 1);
for (c = 0; c < connections->len; c++) { for (c = 0; c < connections->len; c++) {
NMConnection *connection = NM_CONNECTION (connections->pdata[c]); NMConnection *connection = NM_CONNECTION (connections->pdata[c]);
const char *type = nm_connection_get_connection_type (connection); const char *type = nm_connection_get_connection_type (connection);
if (g_strcmp0 (type, NM_SETTING_VPN_SETTING_NAME) == 0) if (g_strcmp0 (type, NM_SETTING_VPN_SETTING_NAME) == 0)
uuids[u++] = nm_connection_get_uuid (connection); array[idx++] = uuid ? nm_connection_get_uuid (connection) : nm_connection_get_id (connection);
} }
uuids[u] = NULL; array[idx] = NULL;
return array;
}
ret = nmc_rl_gen_func_basic (text, state, uuids); static char *
gen_vpn_uuids (const char *text, int state)
{
const GPtrArray *connections = nm_cli.connections;
const char **uuids;
char *ret;
if (connections->len < 1)
return NULL;
uuids = _create_vpn_array (connections, TRUE);
ret = nmc_rl_gen_func_basic (text, state, uuids);
g_free (uuids); g_free (uuids);
return ret; return ret;
} }
static char *
gen_vpn_ids (const char *text, int state)
{
const GPtrArray *connections = nm_cli.connections;
const char **ids;
char *ret;
if (connections->len < 1)
return NULL;
ids = _create_vpn_array (connections, FALSE);
ret = nmc_rl_gen_func_basic (text, state, ids);
g_free (ids);
return ret;
}
static rl_compentry_func_t * static rl_compentry_func_t *
get_gen_func_cmd_nmcli (const char *str) get_gen_func_cmd_nmcli (const char *str)
{ {
...@@ -10092,6 +10133,127 @@ finish: ...@@ -10092,6 +10133,127 @@ finish:
return nmc->return_value; return nmc->return_value;
} }
static NMCResultCode
do_connection_export (NmCli *nmc, int argc, char **argv)
{
NMConnection *connection = NULL;
const char *name;
const char *out_name = NULL;
char *name_ask = NULL;
char *out_name_ask = NULL;
const char *path = NULL;
const char *selector = NULL;
const char *type = NULL;
NMVpnEditorPlugin *plugin;
GError *error = NULL;
if (argc == 0) {
if (nmc->ask) {
name_ask = nmc_readline (PROMPT_VPN_CONNECTION);
name = name_ask = name_ask ? g_strstrip (name_ask) : NULL;
out_name = out_name_ask = nmc_readline (_("Output file name: "));
} else {
g_string_printf (nmc->return_text, _("Error: No arguments provided."));
nmc->return_value = NMC_RESULT_ERROR_USER_INPUT;
goto finish;
}
} else {
if ( strcmp (*argv, "id") == 0
|| strcmp (*argv, "uuid") == 0
|| strcmp (*argv, "path") == 0) {
selector = *argv;
if (next_arg (&argc, &argv) != 0) {
g_string_printf (nmc->return_text, _("Error: %s argument is missing."),
selector);
nmc->return_value = NMC_RESULT_ERROR_USER_INPUT;
goto finish;
}
}
name = *argv;
if (next_arg (&argc, &argv) == 0)
out_name = *argv;
if (next_arg (&argc, &argv) == 0) {
g_string_printf (nmc->return_text, _("Error: unknown extra argument: '%s'."), *argv);
nmc->return_value = NMC_RESULT_ERROR_USER_INPUT;
goto finish;
}
}
if (!name) {
g_string_printf (nmc->return_text, _("Error: connection ID is missing."));
nmc->return_value = NMC_RESULT_ERROR_USER_INPUT;
goto finish;
}
connection = nmc_find_connection (nmc->connections, selector, name, NULL);
if (!connection) {
g_string_printf (nmc->return_text, _("Error: Unknown connection '%s'."), name);
nmc->return_value = NMC_RESULT_ERROR_NOT_FOUND;
goto finish;
}
type = nm_connection_get_connection_type (connection);
if (g_strcmp0 (type, NM_SETTING_VPN_SETTING_NAME) != 0) {
g_string_printf (nmc->return_text, _("Error: the connection is not VPN."));
nmc->return_value = NMC_RESULT_ERROR_USER_INPUT;
goto finish;
}
type = nm_setting_vpn_get_service_type (nm_connection_get_setting_vpn (connection));
/* Export VPN configuration */
plugin = nm_vpn_get_plugin_by_service (type, &error);
if (!plugin) {
g_string_printf (nmc->return_text, _("Error: failed to load VPN plugin."));
nmc->return_value = NMC_RESULT_ERROR_UNKNOWN;
goto finish;
}
if (out_name)
path = out_name;
else {
int fd;
char tmpfile[] = "/tmp/nmcli-export-temp-XXXXXX";
fd = g_mkstemp (tmpfile);
if (fd == -1) {
g_string_printf (nmc->return_text, _("Error: failed to create temporary file %s."), tmpfile);
nmc->return_value = NMC_RESULT_ERROR_UNKNOWN;
goto finish;
}
close (fd);
path = tmpfile;
}
if (!nm_vpn_editor_plugin_export (plugin, path, connection, &error)) {
g_string_printf (nmc->return_text, _("Error: failed to export '%s': %s."),
nm_connection_get_id (connection), error ? error->message : "(unknown)");
nmc->return_value = NMC_RESULT_ERROR_UNKNOWN;
goto finish;
}
/* No output file -> copy data to stdout */
if (!out_name) {
char *contents = NULL;
gsize len = 0;
if (!g_file_get_contents (path, &contents, &len, &error)) {
g_string_printf (nmc->return_text, _("Error: failed to read temporary file '%s': %s."),
path, error->message);
nmc->return_value = NMC_RESULT_ERROR_UNKNOWN;
goto finish;
}
g_print ("%s", contents);
g_free (contents);
}
finish:
if (!out_name && path)
unlink (path);
g_clear_error (&error);
g_free (name_ask);
g_free (out_name_ask);
return nmc->return_value;
}
typedef struct { typedef struct {
NmCli *nmc; NmCli *nmc;
...@@ -10197,6 +10359,8 @@ nmcli_con_tab_completion (const char *text, int start, int end) ...@@ -10197,6 +10359,8 @@ nmcli_con_tab_completion (const char *text, int start, int end)
} else if (g_strcmp0 (rl_prompt, PROMPT_IMPORT_FILE) == 0) { } else if (g_strcmp0 (rl_prompt, PROMPT_IMPORT_FILE) == 0) {
rl_attempted_completion_over = 0; rl_attempted_completion_over = 0;
rl_complete_with_tilde_expansion = 1; rl_complete_with_tilde_expansion = 1;
} else if (g_strcmp0 (rl_prompt, PROMPT_VPN_CONNECTION) == 0) {
generator_func = gen_vpn_ids;
} }
if (generator_func) if (generator_func)
...@@ -10389,6 +10553,8 @@ do_connections (NmCli *nmc, int argc, char **argv) ...@@ -10389,6 +10553,8 @@ do_connections (NmCli *nmc, int argc, char **argv)
next_arg (&argc, &argv); next_arg (&argc, &argv);
} }
nmc->return_value = do_connection_import (nmc, temporary, argc, argv); nmc->return_value = do_connection_import (nmc, temporary, argc, argv);
} else if (matches(*argv, "export") == 0) {
nmc->return_value = do_connection_export (nmc, argc-1, argv+1);
} else { } else {
usage (); usage ();
g_string_printf (nmc->return_text, _("Error: '%s' is not valid 'connection' command."), *argv); g_string_printf (nmc->return_text, _("Error: '%s' is not valid 'connection' command."), *argv);
......
...@@ -882,7 +882,7 @@ _nmcli() ...@@ -882,7 +882,7 @@ _nmcli()
;; ;;
c|co|con|conn|conne|connec|connect|connecti|connectio|connection) c|co|con|conn|conne|connec|connect|connecti|connectio|connection)
if [[ ${#words[@]} -eq 2 ]]; then if [[ ${#words[@]} -eq 2 ]]; then
_nmcli_compl_COMMAND "$command" show up down add modify clone edit delete monitor reload load import _nmcli_compl_COMMAND "$command" show up down add modify clone edit delete monitor reload load import export
elif [[ ${#words[@]} -gt 2 ]]; then elif [[ ${#words[@]} -gt 2 ]]; then
case "$command" in case "$command" in
s|sh|sho|show) s|sh|sho|show)
...@@ -1355,6 +1355,32 @@ _nmcli() ...@@ -1355,6 +1355,32 @@ _nmcli()
return 0 return 0
fi fi
;; ;;
e|ex|exp|expo|expor|export)
if [[ ${#words[@]} -eq 3 ]]; then
_nmcli_compl_COMMAND_nl "${words[2]}" "$(printf "id\nuuid\npath\n%s" "$(_nmcli_con_show NAME)")"
elif [[ ${#words[@]} -gt 3 ]]; then
_nmcli_array_delete_at words 0 1
LONG_OPTIONS=(help)
HELP_ONLY_AS_FIRST=1
_nmcli_compl_OPTIONS
case $? in
0)
return 0
;;
1)
if [[ "$HELP_ONLY_AS_FIRST" == 1 ]]; then
_nmcli_compl_COMMAND_nl "${words[2]}" "$(printf "id\nuuid\npath\n%s" "$(_nmcli_con_show NAME)")" "${LONG_OPTIONS[@]}"
fi
return 0
;;
esac
OPTIONS=(id uuid path)
_nmcli_compl_ARGS_CONNECTION && return 0
return 0
fi
;;
esac esac
fi fi
......
...@@ -831,6 +831,14 @@ so that nmcli could import the data. ...@@ -831,6 +831,14 @@ so that nmcli could import the data.
The imported connection profile will be saved as persistent unless \fI--temporary\fP The imported connection profile will be saved as persistent unless \fI--temporary\fP
option is specified, in which case the new profile won't exist after NetworkManager option is specified, in which case the new profile won't exist after NetworkManager
restart. restart.
.TP
.B export [ id | uuid | path ] <ID> [<output file>]
.br
Export a connection.
.br
Only VPN connections are supported at the moment. A proper VPN plugin has to be
installed so that nmcli could export a connection. If no \fI<output file>\fP is
provided, the VPN configuration data will be printed to standard output.
.RE .RE
.TP .TP
...@@ -1206,6 +1214,10 @@ removes the specified IP address from (static) profile ABC. ...@@ -1206,6 +1214,10 @@ removes the specified IP address from (static) profile ABC.
.IP .IP
imports an OpenVPN configuration to NetworkManager. imports an OpenVPN configuration to NetworkManager.
.IP "\fB\f(CWnmcli con export corp-vpnc /home/joe/corpvpn.conf\fP\fP"
.IP
exports NetworkManager VPN profile corp-vpnc as standard Cisco (vpnc) configuration.
.SH NOTES .SH NOTES
\fInmcli\fP accepts abbreviations, as long as they are a unique prefix in the set \fInmcli\fP accepts abbreviations, as long as they are a unique prefix in the set
of possible options. As new options get added, these abbreviations are not guaranteed of possible options. As new options get added, these abbreviations are not guaranteed
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment