cli: add 'nmcli connection import' (rh #1034105)
authorJiří Klimeš <jklimes@redhat.com>
Mon, 23 Nov 2015 16:21:02 +0000 (17:21 +0100)
committerJiří Klimeš <jklimes@redhat.com>
Mon, 7 Dec 2015 08:11:06 +0000 (09:11 +0100)
Synopsis:
  nmcli connection import [--temporary] type <type> file <file to import>

Only VPN configurations can be imported at the moment.

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

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

index a2250ad..192920e 100644 (file)
@@ -40,6 +40,8 @@ nmcli_SOURCES = \
        \
        $(srcdir)/../common/nm-secret-agent-simple.c \
        $(srcdir)/../common/nm-secret-agent-simple.h \
+       $(srcdir)/../common/nm-vpn-helpers.c \
+       $(srcdir)/../common/nm-vpn-helpers.h \
        $(NULL)
 
 nmcli_LDADD = \
index 100d0a1..1fa93b9 100644 (file)
@@ -36,6 +36,7 @@
 #include "connections.h"
 #include "nm-secret-agent-simple.h"
 #include "polkit-agent.h"
+#include "nm-vpn-helpers.h"
 
 /* define some prompts for connection editor */
 #define EDITOR_PROMPT_SETTING  _("Setting name? ")
@@ -274,7 +275,8 @@ usage (void)
                      "  delete [id | uuid | path] <ID>\n\n"
                      "  monitor [id | uuid | path] <ID> ...\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"));
 }
 
 static void
@@ -522,6 +524,19 @@ usage_connection_load (void)
                      "state.\n\n"));
 }
 
+static void
+usage_connection_import (void)
+{
+       g_printerr (_("Usage: nmcli connection import { ARGUMENTS | help }\n"
+                     "\n"
+                     "ARGUMENTS := [--temporary] type <type> file <file to import>\n"
+                     "\n"
+                     "Import an external/foreign configuration as a NetworkManager connection profile.\n"
+                     "The type of the input file is specified by type option.\n"
+                     "Only VPN configurations are supported at the moment. The configuration\n"
+                     "is imported by NetworkManager VPN plugins.\n\n"));
+}
+
 static gboolean
 usage_connection_second_level (const char *cmd)
 {
@@ -549,6 +564,8 @@ usage_connection_second_level (const char *cmd)
                usage_connection_reload ();
        else if (matches (cmd, "load") == 0)
                usage_connection_load ();
+       else if (matches (cmd, "import") == 0)
+               usage_connection_import ();
        else
                ret = FALSE;
        return ret;
@@ -9967,6 +9984,114 @@ do_connection_load (NmCli *nmc, int argc, char **argv)
        return nmc->return_value;
 }
 
+// FIXME: change the text when non-VPN connection types are supported
+#define PROMPT_IMPORT_TYPE  PROMPT_VPN_TYPE
+#define PROMPT_IMPORT_FILE _("File to import: ")
+
+static NMCResultCode
+do_connection_import (NmCli *nmc, gboolean temporary, int argc, char **argv)
+{
+       GError *error = NULL;
+       const char *type = NULL, *filename = NULL;
+       char *type_ask = NULL, *filename_ask = NULL;
+       AddConnectionInfo *info;
+       NMConnection *connection = NULL;
+       NMVpnEditorPlugin *plugin;
+
+       if (argc == 0) {
+               if (nmc->ask) {
+                       type_ask = nmc_readline (PROMPT_IMPORT_TYPE);
+                       filename_ask = nmc_readline (PROMPT_IMPORT_FILE);
+                       type = type_ask = type_ask ? g_strstrip (type_ask) : NULL;
+                       filename = filename_ask = filename_ask ? g_strstrip (filename_ask) : NULL;
+               } else {
+                       g_string_printf (nmc->return_text, _("Error: No arguments provided."));
+                       nmc->return_value = NMC_RESULT_ERROR_USER_INPUT;
+                       goto finish;
+               }
+       }
+
+       while (argc > 0) {
+               if (strcmp (*argv, "type") == 0) {
+                       if (next_arg (&argc, &argv) != 0) {
+                               g_string_printf (nmc->return_text, _("Error: %s argument is missing."), *(argv-1));
+                               nmc->return_value = NMC_RESULT_ERROR_USER_INPUT;
+                               goto finish;
+                       }
+                       if (!type)
+                               type = *argv;
+                       else
+                               g_printerr (_("Warning: 'type' already specified, ignoring extra one.\n"));
+
+               } else if (strcmp (*argv, "file") == 0) {
+                       if (next_arg (&argc, &argv) != 0) {
+                               g_string_printf (nmc->return_text, _("Error: %s argument is missing."), *(argv-1));
+                               nmc->return_value = NMC_RESULT_ERROR_USER_INPUT;
+                               goto finish;
+                       }
+                       if (!filename)
+                               filename = *argv;
+                       else
+                               g_printerr (_("Warning: 'file' already specified, ignoring extra one.\n"));
+               } else {
+                       g_string_printf (nmc->return_text, _("Unknown parameter: %s\n"), *argv);
+                       nmc->return_value = NMC_RESULT_ERROR_USER_INPUT;
+                       goto finish;
+               }
+
+               argc--;
+               argv++;
+       }
+
+       if (!type) {
+               g_string_printf (nmc->return_text, _("Error: 'type' argument is required."));
+               nmc->return_value = NMC_RESULT_ERROR_USER_INPUT;
+               goto finish;
+       }
+       if (!filename) {
+               g_string_printf (nmc->return_text, _("Error: 'file' argument is required."));
+               nmc->return_value = NMC_RESULT_ERROR_USER_INPUT;
+               goto finish;
+       }
+
+       /* Import 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: %s."),
+                                error->message);
+               nmc->return_value = NMC_RESULT_ERROR_UNKNOWN;
+               goto finish;
+       }
+
+       connection = nm_vpn_editor_plugin_import (plugin, filename, &error);
+       if (!connection) {
+               g_string_printf (nmc->return_text, _("Error: failed to import '%s': %s."),
+                                filename, error->message);
+               nmc->return_value = NMC_RESULT_ERROR_UNKNOWN;
+               goto finish;
+       }
+
+       info = g_malloc0 (sizeof (AddConnectionInfo));
+       info->nmc = nmc;
+       info->con_name = g_strdup (nm_connection_get_id (connection));
+
+       /* Add the new imported connection to NetworkManager */
+       add_new_connection (!temporary,
+                           nmc->client,
+                           connection,
+                           add_connection_cb,
+                           info);
+
+       nmc->should_wait = TRUE;
+finish:
+       if (connection)
+               g_object_unref (connection);
+       g_clear_error (&error);
+       g_free (type_ask);
+       g_free (filename_ask);
+       return nmc->return_value;
+}
+
 
 typedef struct {
        NmCli *nmc;
@@ -10067,6 +10192,11 @@ nmcli_con_tab_completion (const char *text, int start, int end)
                generator_func = gen_func_connection_names;
        } else if (g_strcmp0 (rl_prompt, PROMPT_ACTIVE_CONNECTIONS) == 0) {
                generator_func = gen_func_active_connection_names;
+       } else if (g_strcmp0 (rl_prompt, PROMPT_IMPORT_TYPE) == 0) {
+               generator_func = gen_func_vpn_types;
+       } else if (g_strcmp0 (rl_prompt, PROMPT_IMPORT_FILE) == 0) {
+               rl_attempted_completion_over = 0;
+               rl_complete_with_tilde_expansion = 1;
        }
 
        if (generator_func)
@@ -10250,6 +10380,15 @@ do_connections (NmCli *nmc, int argc, char **argv)
                                next_arg (&argc, &argv);
                        }
                        nmc->return_value = do_connection_clone (nmc, temporary, argc, argv);
+               } else if (matches(*argv, "import") == 0) {
+                       gboolean temporary = FALSE;
+
+                       next_arg (&argc, &argv);
+                       if (nmc_arg_is_option (*argv, "temporary")) {
+                               temporary = TRUE;
+                               next_arg (&argc, &argv);
+                       }
+                       nmc->return_value = do_connection_import (nmc, temporary, argc, argv);
                } else {
                        usage ();
                        g_string_printf (nmc->return_text, _("Error: '%s' is not valid 'connection' command."), *argv);
index 63050a3..c3630c2 100644 (file)
@@ -282,6 +282,7 @@ _nmcli_compl_OPTIONS()
 # expects several options with parameters. This function can parse them and remove them from the words array.
 _nmcli_compl_ARGS()
 {
+    local aliases=${@}
     local OPTIONS_ALL N_REMOVE_WORDS REMOVE_OPTIONS OPTIONS_HAS_MANDATORY i
     OPTIONS_ALL=("${OPTIONS[@]}")
     OPTIONS_UNKNOWN_OPTION=
@@ -317,7 +318,17 @@ _nmcli_compl_ARGS()
 
         N_REMOVE_WORDS=2
         REMOVE_OPTIONS=("${words[0]}")
-        case "${words[0]}" in
+
+        # change option name to alias
+        WORD0="${words[0]}"
+        for alias in "${aliases[@]}" ; do
+            if [[ "${WORD0}" == ${alias%%:*} ]]; then
+                WORD0=${alias#*:}
+                break
+            fi
+        done
+
+        case "${WORD0}" in
             level)
                 if [[ "${#words[@]}" -eq 2 ]]; then
                     _nmcli_list "OFF ERR WARN INFO DEBUG TRACE"
@@ -560,7 +571,8 @@ _nmcli_compl_ARGS()
             username| \
             service| \
             password| \
-            passwd-file)
+            passwd-file| \
+            file)
                 if [[ "${#words[@]}" -eq 2 ]]; then
                     return 0
                 fi
@@ -870,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
+                _nmcli_compl_COMMAND "$command" show up down add modify clone edit delete monitor reload load import
             elif [[ ${#words[@]} -gt 2 ]]; then
                 case "$command" in
                     s|sh|sho|show)
@@ -1315,6 +1327,35 @@ _nmcli()
                             COMPREPLY=()
                         fi
                         ;;
+                    i|im|imp|impo|impor|import)
+                        if [[ ${#words[@]} -eq 3 ]]; then
+                            _nmcli_compl_COMMAND "${words[2]}" type file --temporary
+                        elif [[ ${#words[@]} -gt 3 ]]; then
+                            _nmcli_array_delete_at words 0 1
+
+                            LONG_OPTIONS=(help temporary)
+                            HELP_ONLY_AS_FIRST=1
+                            _nmcli_compl_OPTIONS
+                            case $? in
+                                0)
+                                    return 0
+                                    ;;
+                                1)
+                                    if [[ "$HELP_ONLY_AS_FIRST" == 1 ]]; then
+                                        _nmcli_compl_COMMAND "${words[2]}" type file
+                                    fi
+                                    return 0
+                                    ;;
+                            esac
+
+                            OPTIONS=(type file)
+                            OPTIONS_MANDATORY=(type file)
+                            ALIASES=("type:vpn-type")
+                            _nmcli_compl_ARGS ${ALIASES[@]}
+                            return 0
+                        fi
+                        ;;
+
                 esac
             fi
             ;;
index 6e8881b..180dd10 100644 (file)
@@ -816,6 +816,21 @@ then \fINetworkManager\fP will reload connection files any time they change
 Load/reload one or more connection files from disk. Use this after manually
 editing a connection file to ensure that \fBNetworkManager\fP is aware
 of its latest state.
+.TP
+.B import [--temporary] type <type> file <file to import>
+.br
+Import an external/foreign configuration as a NetworkManager connection profile.
+The type of the input file is specified by \fItype\fP option.
+.br
+Only VPN configurations are supported at the moment. The configuration
+is imported by NetworkManager VPN plugins. \fItype\fP values are the same as for
+\fIvpn-type\fP option in \fBnmcli connection add\fP. VPN configurations are
+imported by VPN plugins. Therefore the proper VPN plugin has to be installed
+so that nmcli could import the data.
+.br
+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.
 .RE
 
 .TP
@@ -1187,6 +1202,10 @@ appends a Google public DNS server to DNS servers in ABC profile.
 .IP
 removes the specified IP address from (static) profile ABC.
 
+.IP "\fB\f(CWnmcli con import type openvpn file ~/Downloads/frootvpn.ovpn\fP\fP"
+.IP
+imports an OpenVPN configuration to NetworkManager.
+
 .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