summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJiří Klimeš <jklimes@redhat.com>2015-11-05 12:03:49 +0100
committerJiří Klimeš <jklimes@redhat.com>2015-11-06 13:00:17 +0100
commit7377459e4750b1d58344c6e01a7ab5cb926aaeb4 (patch)
tree3880270a91676e7c5bf566162f5ca9149022b4e0
parentb9da3d93207e46de895fd07cfe9de1edfa79efef (diff)
downloadNetworkManager-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.c160
-rw-r--r--clients/cli/nmcli-completion30
-rw-r--r--man/nmcli.1.in14
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