cli: add 'nmcli connection export' (rh #1034105)
authorJiří Klimeš <jklimes@redhat.com>
Tue, 24 Nov 2015 13:14:53 +0000 (14:14 +0100)
committerJiří Klimeš <jklimes@redhat.com>
Mon, 7 Dec 2015 08:11:20 +0000 (09:11 +0100)
Synopsis:
  nmcli connection export [ id | uuid | path] <ID> [<output file>]

for exporting VPN connections.

https://bugzilla.redhat.com/show_bug.cgi?id=1034105

clients/cli/connections.c
clients/cli/nmcli-completion
man/nmcli.1.in

index 1fa93b9..870b61f 100644 (file)
@@ -48,6 +48,7 @@
 #define PROMPT_VPN_TYPE    _("VPN type: ")
 #define PROMPT_MASTER      _("Master: ")
 #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_ACTIVE_CONNECTIONS _("Connection(s) (name, UUID, path or apath): ")
 
@@ -276,7 +277,8 @@ usage (void)
                      "  monitor [id | uuid | path] <ID> ...\n\n"
                      "  reload\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
@@ -537,6 +539,17 @@ usage_connection_import (void)
                      "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
 usage_connection_second_level (const char *cmd)
 {
@@ -566,6 +579,8 @@ usage_connection_second_level (const char *cmd)
                usage_connection_load ();
        else if (matches (cmd, "import") == 0)
                usage_connection_import ();
+       else if (matches (cmd, "export") == 0)
+               usage_connection_export ();
        else
                ret = FALSE;
        return ret;
@@ -6885,33 +6900,59 @@ gen_compat_devices (const char *text, int state)
        return ret;
 }
 
-static char *
-gen_vpn_uuids (const char *text, int state)
+static const char **
+_create_vpn_array (const GPtrArray *connections, gboolean uuid)
 {
-       const GPtrArray *connections = nmc_tab_completion.nmc->connections;
-       int c, u = 0;
-       const char **uuids;
-       char *ret;
+       int c, idx = 0;
+       const char **array;
 
        if (connections->len < 1)
                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++) {
                NMConnection *connection = NM_CONNECTION (connections->pdata[c]);
                const char *type = nm_connection_get_connection_type (connection);
 
                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);
        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 *
 get_gen_func_cmd_nmcli (const char *str)
 {
@@ -10092,6 +10133,127 @@ finish:
        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 {
        NmCli *nmc;
@@ -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) {
                rl_attempted_completion_over = 0;
                rl_complete_with_tilde_expansion = 1;
+       } else if (g_strcmp0 (rl_prompt, PROMPT_VPN_CONNECTION) == 0) {
+               generator_func = gen_vpn_ids;
        }
 
        if (generator_func)
@@ -10389,6 +10553,8 @@ do_connections (NmCli *nmc, int argc, char **argv)
                                next_arg (&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 {
                        usage ();
                        g_string_printf (nmc->return_text, _("Error: '%s' is not valid 'connection' command."), *argv);
index c3630c2..e5aae8d 100644 (file)
@@ -882,7 +882,7 @@ _nmcli()
             ;;
         c|co|con|conn|conne|connec|connect|connecti|connectio|connection)
             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
                 case "$command" in
                     s|sh|sho|show)
@@ -1355,6 +1355,32 @@ _nmcli()
                             return 0
                         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
             fi
index 180dd10..35edc71 100644 (file)
@@ -831,6 +831,14 @@ so that nmcli could import the data.
 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
 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
 
 .TP
@@ -1206,6 +1214,10 @@ removes the specified IP address from (static) profile ABC.
 .IP
 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
 \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