cli: support file names for 'config' argument when creating team connections

nmcli con add type team config /home/cimrman/team-config.json

libteam (and in turn NetworkManager) configures team devices via plain config
data in JSON format. However, it is useful and more user-friendly for nmcli to
accept also a file name that contains the config data, and read it. Thus the
user is not forced to type whole (possibly long) config on the command line.
This commit is contained in:
Jiří Klimeš
2013-11-11 17:48:25 +01:00
parent 7994778723
commit a3c06afc12
4 changed files with 163 additions and 17 deletions

View File

@@ -891,3 +891,36 @@ nmc_bond_validate_mode (const char *mode, GError **error)
return nmc_string_is_valid (mode, valid_modes, error); return nmc_string_is_valid (mode, valid_modes, error);
} }
gboolean
nmc_team_check_config (const char *config, char **out_config, GError **error)
{
char *contents = NULL;
size_t c_len = 0;
*out_config = NULL;
if (!config || strlen (config) == strspn (config, " \t"))
return TRUE;
/* 'config' can be either a file name or raw JSON config data */
if (g_file_test (config, G_FILE_TEST_EXISTS))
g_file_get_contents (config, &contents, NULL, NULL);
else
contents = g_strdup (config);
if (contents) {
g_strstrip (contents);
c_len = strlen (contents);
}
/* Do a simple validity check */
if (!contents || !contents[0] || c_len > 100000 || contents[0] != '{' || contents[c_len-1] != '}') {
g_set_error (error, NMCLI_ERROR, NMC_RESULT_ERROR_USER_INPUT,
_("'%s' is not a valid team configuration or file name."), config);
g_free (contents);
return FALSE;
}
*out_config = contents;
return TRUE;
}

View File

@@ -16,7 +16,7 @@
* with this program; if not, write to the Free Software Foundation, Inc., * with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
* *
* (C) Copyright 2012 Red Hat, Inc. * (C) Copyright 2012 - 2013 Red Hat, Inc.
*/ */
#ifndef NMC_COMMON_H #ifndef NMC_COMMON_H
@@ -52,5 +52,6 @@ nmc_vlan_parse_priority_maps (const char *priority_map,
GError **error); GError **error);
const char *nmc_bond_validate_mode (const char *mode, GError **error); const char *nmc_bond_validate_mode (const char *mode, GError **error);
gboolean nmc_team_check_config (const char *config, char **out_config, GError **error);
#endif /* NMC_COMMON_H */ #endif /* NMC_COMMON_H */

View File

@@ -280,9 +280,9 @@ usage_connection_add (void)
" [arp-interval <num>]\n" " [arp-interval <num>]\n"
" [arp-ip-target <num>]\n\n" " [arp-ip-target <num>]\n\n"
" bond-slave: master <master (ifname or connection UUID)>\n\n" " bond-slave: master <master (ifname or connection UUID)>\n\n"
" team: [config <json config>]\n\n" " team: [config <file>|<raw JSON data>]\n\n"
" team-slave: master <master (ifname or connection UUID)>\n" " team-slave: master <master (ifname or connection UUID)>\n"
" [config <json config>]\n\n" " [config <file>|<raw JSON data>]\n\n"
" bridge: [stp yes|no>]\n" " bridge: [stp yes|no>]\n"
" [priority <num>]\n" " [priority <num>]\n"
" [forward-delay <2-30>]\n" " [forward-delay <2-30>]\n"
@@ -2766,6 +2766,53 @@ do_questionnaire_bond (char **mode, char **primary, char **miimon,
return; return;
} }
static void
do_questionnaire_team_common (const char *type_name, char **config)
{
char *answer;
gboolean answer_bool;
gboolean once_more;
char *json = NULL;
GError *error = NULL;
/* Ask for optional 'team' arguments. */
printf (_("There is 1 optional argument for '%s' connection type.\n"), type_name);
answer = nmc_get_user_input (_("Do you want to provide it? (yes/no) [yes] "));
if (answer && (!nmc_string_to_bool (answer, &answer_bool, NULL) || !answer_bool)) {
g_free (answer);
return;
}
if (!*config) {
do {
*config = nmc_get_user_input (_("Team JSON configuration [none]: "));
once_more = !nmc_team_check_config (*config, &json, &error);
if (once_more) {
printf ("Error: %s\n", error->message);
g_clear_error (&error);
g_free (*config);
}
} while (once_more);
}
*config = json;
g_free (answer);
return;
}
/* Both team and team-slave curently have just ithe same one optional argument */
static void
do_questionnaire_team (char **config)
{
do_questionnaire_team_common (_("team"), config);
}
static void
do_questionnaire_team_slave (char **config)
{
do_questionnaire_team_common (_("team-slave"), config);
}
static void static void
do_questionnaire_bridge (char **stp, char **priority, char **fwd_delay, do_questionnaire_bridge (char **stp, char **priority, char **fwd_delay,
char **hello_time, char **max_age, char **ageing_time) char **hello_time, char **max_age, char **ageing_time)
@@ -3792,15 +3839,23 @@ cleanup_bond:
} else if (!strcmp (con_type, NM_SETTING_TEAM_SETTING_NAME)) { } else if (!strcmp (con_type, NM_SETTING_TEAM_SETTING_NAME)) {
/* Build up the settings required for 'team' */ /* Build up the settings required for 'team' */
gboolean success = FALSE;
char *team_ifname = NULL; char *team_ifname = NULL;
const char *ifname = NULL; const char *ifname = NULL;
const char *config = NULL; const char *config_c = NULL;
nmc_arg_t exp_args[] = { {"config", TRUE, &config, FALSE}, char *config = NULL;
char *json = NULL;
nmc_arg_t exp_args[] = { {"config", TRUE, &config_c, FALSE},
{NULL} }; {NULL} };
if (!nmc_parse_args (exp_args, FALSE, &argc, &argv, error)) if (!nmc_parse_args (exp_args, FALSE, &argc, &argv, error))
return FALSE; return FALSE;
/* Also ask for all optional arguments if '--ask' is specified. */
config = g_strdup (config_c);
if (ask)
do_questionnaire_team (&config);
/* Use connection's ifname as 'team' ifname if exists, else generate one */ /* Use connection's ifname as 'team' ifname if exists, else generate one */
ifname = nm_setting_connection_get_interface_name (s_con); ifname = nm_setting_connection_get_interface_name (s_con);
if (!ifname) if (!ifname)
@@ -3815,22 +3870,35 @@ cleanup_bond:
s_team = (NMSettingTeam *) nm_setting_team_new (); s_team = (NMSettingTeam *) nm_setting_team_new ();
nm_connection_add_setting (connection, NM_SETTING (s_team)); nm_connection_add_setting (connection, NM_SETTING (s_team));
if (!nmc_team_check_config (config, &json, error)) {
g_prefix_error (error, _("Error: "));
goto cleanup_team;
}
/* Set team options */ /* Set team options */
g_object_set (s_team, NM_SETTING_TEAM_INTERFACE_NAME, team_ifname, NULL); g_object_set (s_team, NM_SETTING_TEAM_INTERFACE_NAME, team_ifname, NULL);
if (config) g_object_set (s_team, NM_SETTING_TEAM_CONFIG, json, NULL);
g_object_set (s_team, NM_SETTING_TEAM_CONFIG, config, NULL);
success = TRUE;
cleanup_team:
g_free (team_ifname); g_free (team_ifname);
g_free (config);
g_free (json);
if (!success)
return FALSE;
} else if (!strcmp (con_type, "team-slave")) { } else if (!strcmp (con_type, "team-slave")) {
/* Build up the settings required for 'team-slave' */ /* Build up the settings required for 'team-slave' */
gboolean success = FALSE;
const char *master = NULL; const char *master = NULL;
char *master_ask = NULL; char *master_ask = NULL;
const char *type = NULL; const char *type = NULL;
const char *config = NULL; const char *config_c = NULL;
char *config = NULL;
char *json = NULL;
nmc_arg_t exp_args[] = { {"master", TRUE, &master, !ask}, nmc_arg_t exp_args[] = { {"master", TRUE, &master, !ask},
{"type", TRUE, &type, FALSE}, {"type", TRUE, &type, FALSE},
{"config", TRUE, &config, FALSE}, {"config", TRUE, &config_c, FALSE},
{NULL} }; {NULL} };
if (!nmc_parse_args (exp_args, TRUE, &argc, &argv, error)) if (!nmc_parse_args (exp_args, TRUE, &argc, &argv, error))
@@ -3844,6 +3912,11 @@ cleanup_bond:
return FALSE; return FALSE;
} }
/* Also ask for all optional arguments if '--ask' is specified. */
config = g_strdup (config_c);
if (ask)
do_questionnaire_team_slave (&config);
if (type) if (type)
printf (_("Warning: 'type' is currently ignored. " printf (_("Warning: 'type' is currently ignored. "
"We only support ethernet slaves for now.\n")); "We only support ethernet slaves for now.\n"));
@@ -3852,8 +3925,13 @@ cleanup_bond:
s_team_port = (NMSettingTeamPort *) nm_setting_team_port_new (); s_team_port = (NMSettingTeamPort *) nm_setting_team_port_new ();
nm_connection_add_setting (connection, NM_SETTING (s_team_port)); nm_connection_add_setting (connection, NM_SETTING (s_team_port));
if (config) if (!nmc_team_check_config (config, &json, error)) {
g_object_set (s_team_port, NM_SETTING_TEAM_PORT_CONFIG, config, NULL); g_prefix_error (error, _("Error: "));
goto cleanup_team_slave;
}
/* Set team-port options */
g_object_set (s_team_port, NM_SETTING_TEAM_PORT_CONFIG, json, NULL);
/* Change properties in 'connection' setting */ /* Change properties in 'connection' setting */
g_object_set (s_con, g_object_set (s_con,
@@ -3866,7 +3944,13 @@ cleanup_bond:
s_wired = (NMSettingWired *) nm_setting_wired_new (); s_wired = (NMSettingWired *) nm_setting_wired_new ();
nm_connection_add_setting (connection, NM_SETTING (s_wired)); nm_connection_add_setting (connection, NM_SETTING (s_wired));
success = TRUE;
cleanup_team_slave:
g_free (master_ask); g_free (master_ask);
g_free (config);
g_free (json);
if (!success)
return FALSE;
} else if (!strcmp (con_type, NM_SETTING_BRIDGE_SETTING_NAME)) { } else if (!strcmp (con_type, NM_SETTING_BRIDGE_SETTING_NAME)) {
/* Build up the settings required for 'bridge' */ /* Build up the settings required for 'bridge' */

View File

@@ -3100,6 +3100,34 @@ nmc_property_serial_set_parity (NMSetting *setting, const char *prop, const char
return TRUE; return TRUE;
} }
/* --- NM_SETTING_TEAM_SETTING_NAME property functions --- */
/* --- NM_SETTING_TEAM_PORT_SETTING_NAME property functions --- */
static gboolean
nmc_property_team_set_config (NMSetting *setting, const char *prop, const char *val, GError **error)
{
char *json = NULL;
g_return_val_if_fail (error == NULL || *error == NULL, FALSE);
if (!nmc_team_check_config (val, &json, error)) {
return FALSE;
}
g_object_set (setting, prop, json, NULL);
g_free (json);
return TRUE;
}
static const char *
nmc_property_team_describe_config (NMSetting *setting, const char *prop)
{
return _("nmcli can accepts both direct JSON configuration data and a file name containing "
"the configuration. In the latter case the file is read and the contents is put "
"into this property.\n\n"
"Examples: set team.config "
"{ \"device\": \"team0\", \"runner\": {\"name\": \"roundrobin\"}, \"ports\": {\"eth1\": {}, \"eth2\": {}} }\n"
" set team.config /etc/my-team.conf\n");
}
/* --- NM_SETTING_VLAN_SETTING_NAME property setter functions --- */ /* --- NM_SETTING_VLAN_SETTING_NAME property setter functions --- */
static gboolean static gboolean
nmc_property_vlan_set_prio_map (NMSetting *setting, nmc_property_vlan_set_prio_map (NMSetting *setting,
@@ -4590,18 +4618,18 @@ nmc_properties_init (void)
NULL); NULL);
nmc_add_prop_funcs (GLUE (TEAM, CONFIG), nmc_add_prop_funcs (GLUE (TEAM, CONFIG),
nmc_property_team_get_config, nmc_property_team_get_config,
nmc_property_set_string, nmc_property_team_set_config,
NULL,
NULL, NULL,
nmc_property_team_describe_config,
NULL, NULL,
NULL); NULL);
/* Add editable properties for NM_SETTING_TEAM_PORT_SETTING_NAME */ /* Add editable properties for NM_SETTING_TEAM_PORT_SETTING_NAME */
nmc_add_prop_funcs (GLUE (TEAM_PORT, CONFIG), nmc_add_prop_funcs (GLUE (TEAM_PORT, CONFIG),
nmc_property_team_port_get_config, nmc_property_team_port_get_config,
nmc_property_set_string, nmc_property_team_set_config,
NULL,
NULL, NULL,
nmc_property_team_describe_config,
NULL, NULL,
NULL); NULL);