diff options
author | Jiří Klimeš <jklimes@redhat.com> | 2015-11-05 12:03:49 +0100 |
---|---|---|
committer | Jiří Klimeš <jklimes@redhat.com> | 2015-11-06 13:00:17 +0100 |
commit | 7377459e4750b1d58344c6e01a7ab5cb926aaeb4 (patch) | |
tree | 3880270a91676e7c5bf566162f5ca9149022b4e0 | |
parent | b9da3d93207e46de895fd07cfe9de1edfa79efef (diff) | |
download | NetworkManager-jk/nmcli-con-clone.tar.gz |
cli: add ' nmcli connection clone' command for cloning connectionsjk/nmcli-con-clone
Synopsis:
nmcli connection clone [--temporary] [id|uuid|path] <ID> <new name>
It copies the <ID> connection as <new name>. The command is very useful
if there is a connection, but another one is needed for a related
configuration. One can copy the existing profile and modify it for the
new situation.
For example:
$ nmcli con clone main-eth second-eth
$ nmcli con mod second-eth connection.interface-name em4
-rw-r--r-- | clients/cli/connections.c | 160 | ||||
-rw-r--r-- | clients/cli/nmcli-completion | 30 | ||||
-rw-r--r-- | man/nmcli.1.in | 14 |
3 files changed, 203 insertions, 1 deletions
diff --git a/clients/cli/connections.c b/clients/cli/connections.c index 94d0ff73c2..d4df35e4be 100644 --- a/clients/cli/connections.c +++ b/clients/cli/connections.c @@ -251,6 +251,7 @@ usage (void) " down [id | uuid | path | apath] <ID> ...\n\n" " add COMMON_OPTIONS TYPE_SPECIFIC_OPTIONS SLAVE_OPTIONS IP_OPTIONS [-- ([+|-]<setting>.<property> <value>)+]\n\n" " modify [--temporary] [id | uuid | path] <ID> ([+|-]<setting>.<property> <value>)+\n\n" + " clone [--temporary] [id | uuid | path ] <ID> <new name>\n\n" " edit [id | uuid | path] <ID>\n" " edit [type <new_con_type>] [con-name <new_con_name>]\n\n" " delete [id | uuid | path] <ID>\n\n" @@ -428,6 +429,18 @@ usage_connection_modify (void) } static void +usage_connection_clone (void) +{ + g_printerr (_("Usage: nmcli connection clone { ARGUMENTS | help }\n" + "\n" + "ARGUMENTS := [--temporary] [id | uuid | path] <ID> <new name>\n" + "\n" + "Clone an existing connection profile. The newly created connection will be\n" + "the exact copy of the <ID>, except the uuid property (will be generated) and\n" + "id (provided as <new name> argument).\n\n")); +} + +static void usage_connection_edit (void) { g_printerr (_("Usage: nmcli connection edit { ARGUMENTS | help }\n" @@ -488,6 +501,8 @@ usage_connection_second_level (const char *cmd) usage_connection_add (); else if (matches (cmd, "modify") == 0) usage_connection_modify (); + else if (matches (cmd, "clone") == 0) + usage_connection_clone (); else if (matches (cmd, "edit") == 0) usage_connection_edit (); else if (matches (cmd, "delete") == 0) @@ -9169,6 +9184,142 @@ finish: return nmc->return_value; } +typedef struct { + NmCli *nmc; + char *orig_id; + char *orig_uuid; + char *con_id; +} CloneConnectionInfo; + +static void +clone_connection_cb (GObject *client, + GAsyncResult *result, + gpointer user_data) +{ + CloneConnectionInfo *info = (CloneConnectionInfo *) user_data; + NmCli *nmc = info->nmc; + NMRemoteConnection *connection; + GError *error = NULL; + + connection = nm_client_add_connection_finish (NM_CLIENT (client), result, &error); + if (error) { + g_string_printf (nmc->return_text, + _("Error: Failed to add '%s' connection: %s"), + info->con_id, error->message); + g_error_free (error); + nmc->return_value = NMC_RESULT_ERROR_CON_ACTIVATION; + } else { + g_print (_("%s (%s) cloned as %s (%s).\n"), + info->orig_id, + info->orig_uuid, + nm_connection_get_id (NM_CONNECTION (connection)), + nm_connection_get_uuid (NM_CONNECTION (connection))); + g_object_unref (connection); + } + + g_free (info->con_id); + g_free (info->orig_id); + g_free (info->orig_uuid); + g_slice_free (CloneConnectionInfo, info); + quit (); +} + +static NMCResultCode +do_connection_clone (NmCli *nmc, gboolean temporary, int argc, char **argv) +{ + NMConnection *connection = NULL; + NMConnection *new_connection = NULL; + NMSettingConnection *s_con; + CloneConnectionInfo *info; + const char *name; + const char *new_name; + char *name_ask = NULL; + char *new_name_ask = NULL; + const char *selector = NULL; + char *uuid; + + if (argc == 0) { + if (nmc->ask) { + name = name_ask = nmc_readline (PROMPT_CONNECTION); + new_name = new_name_ask = nmc_readline ("New connection 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; + } + name = *argv; + if (!name) { + g_string_printf (nmc->return_text, _("Error: connection ID is missing.")); + nmc->return_value = NMC_RESULT_ERROR_USER_INPUT; + goto finish; + } + if (next_arg (&argc, &argv) != 0) { + g_string_printf (nmc->return_text, _("Error: <new name> argument is missing.")); + nmc->return_value = NMC_RESULT_ERROR_USER_INPUT; + goto finish; + } + new_name = *argv; + } + + 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; + } + + /* Copy the connection */ + new_connection = nm_simple_connection_new_clone (connection); + + s_con = nm_connection_get_setting_connection (new_connection); + g_assert (s_con); + uuid = nm_utils_uuid_generate (); + g_object_set (s_con, + NM_SETTING_CONNECTION_ID, new_name, + NM_SETTING_CONNECTION_UUID, uuid, + NULL); + g_free (uuid); + + /* Merge secrets into the new connection */ + update_secrets_in_connection (NM_REMOTE_CONNECTION (connection), new_connection); + + info = g_slice_new0 (CloneConnectionInfo); + info->nmc = nmc; + info->orig_id = g_strdup (nm_connection_get_id (connection)); + info->orig_uuid = g_strdup (nm_connection_get_uuid (connection)); + info->con_id = g_strdup (nm_connection_get_id (new_connection)); + + /* Add the new cloned connection to NetworkManager */ + add_new_connection (!temporary, + nmc->client, + new_connection, + clone_connection_cb, + info); + + nmc->should_wait = TRUE; +finish: + if (new_connection) + g_object_unref (new_connection); + g_free (name_ask); + g_free (new_name_ask); + + return nmc->return_value; +} + static void delete_cb (GObject *con, GAsyncResult *result, gpointer user_data) { @@ -9628,6 +9779,15 @@ do_connections (NmCli *nmc, int argc, char **argv) next_arg (&argc, &argv); } nmc->return_value = do_connection_modify (nmc, temporary, argc, argv); + } else if (matches (*argv, "clone") == 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_clone (nmc, temporary, argc, argv); } else { usage (); g_string_printf (nmc->return_text, _("Error: '%s' is not valid 'connection' command."), *argv); diff --git a/clients/cli/nmcli-completion b/clients/cli/nmcli-completion index 6d9b6ae1cb..203a020716 100644 --- a/clients/cli/nmcli-completion +++ b/clients/cli/nmcli-completion @@ -863,7 +863,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 edit delete reload load + _nmcli_compl_COMMAND "$command" show up down add modify clone edit delete reload load elif [[ ${#words[@]} -gt 2 ]]; then case "$command" in s|sh|sho|show) @@ -1245,6 +1245,34 @@ _nmcli() return 0 fi ;; + c|cl|clo|clon|clone) + if [[ ${#words[@]} -eq 3 ]]; then + _nmcli_compl_COMMAND_nl "${words[2]}" "$(printf "id\nuuid\npath\n%s" "$(_nmcli_con_show NAME)")" 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_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 + ;; + de|del|dele|delet|delete) if [[ ${#words[@]} -eq 3 ]]; then _nmcli_compl_COMMAND_nl "${words[2]}" "$(printf "id\nuuid\npath\n%s" "$(_nmcli_con_show NAME)")" diff --git a/man/nmcli.1.in b/man/nmcli.1.in index 1ecfd80aff..5351870780 100644 --- a/man/nmcli.1.in +++ b/man/nmcli.1.in @@ -739,6 +739,20 @@ The changes to the connection profile will be saved persistently by NetworkManager, unless \fI--temporary\fP option is provided, in which case the changes won't persist over NetworkManager restart. .TP +.B clone [--temporary] [ id | uuid | path ] <ID> <new name> +.br +Clone a connection. The connection to be cloned is identified by its +name, UUID or D-Bus path. If <ID> is ambiguous, a keyword \fIid\fP, +\fIuuid\fP or \fIpath\fP can be used. See \fBconnection show\fP above for +the description of the <ID>-specifying keywords. \fI<new name>\fP is the name +of the new cloned connection. The new connection will be the exact copy except +the connection.id (\fI<new name>\fP) and connection.uuid (generated) +properties. +.br +The new connection profile will be saved as persistent unless \fI--temporary\fP +option is specified, in which case the new profile won't outlive NetworkManager +restart. +.TP .B delete [ id | uuid | path ] <ID> ... .br Delete a configured connection. The connection to be deleted is identified by |