diff --git a/cli/dcoscli/config/main.py b/cli/dcoscli/config/main.py index cf7cfa0d4..2300d40b3 100644 --- a/cli/dcoscli/config/main.py +++ b/cli/dcoscli/config/main.py @@ -110,6 +110,72 @@ def _unset(name): return 0 +def _format_config(file_value, effective_value, name=None, envvar_name=None): + """ + Construct a string to show on a terminal, indicating the value and + possibly other useful things such as the setting name and whether + it is being controlled by an environment variable. + + >>> _format_config('x', 'x') + 'x' + >>> _format_config('x', 'x', 'setting.name') -> + 'setting.name x' + >>> _format_config('x', 'y', envvar_name='ENVVAR') + 'y # overwritten by ENVVAR; config file value: x' + >>> _format_config('x', 'y', 'setting.name', envvar_name='ENVVAR') + 'setting.name y # overwritten by ENVVAR; config file value: x' + + :param file_value: config value present in the toml file + :type file_value: str + + :param effective_value: config value either from file or as overwritten + from the environment + :type effective_value: str + :param name: config key (not used for single value show) + :type name: str|None + :param envvar_name: name of environment variable that overwote the value + :type envvar_name: str|None + :returns: formatted string for terminal + :rtype: str + """ + + # when declaring that vars are overwritten by the environment, + # line up those messages to this column (unless the var name is long) + overwite_msg_display_column = 35 + + # When showing all values, don't print the token value; + if name == "core.dcos_acs_token": + print_value = "*"*8 + else: + print_value = effective_value + + if file_value == effective_value: + if name: + return '%s %s' % (name, print_value) + else: + return effective_value + else: + if not envvar_name: + envvar_name = "N/A" # this should never happen + if name: + s = '%s %s' % (name, print_value) + else: + s = effective_value + + left_pad_fmt = '%-{}s'.format(overwite_msg_display_column) # '%-35s' + + msg_start = left_pad_fmt + ' # overwritten by environment var %s; ' + + if print_value != effective_value: + # We're obscuring the effective security token + # so don't report the file value either + msg = msg_start + "config file value differs" + return msg % (s, envvar_name) + + msg = msg_start + 'config file value: %s' + return msg % (s, envvar_name, file_value) + + def _show(name): """ :returns: process status @@ -119,19 +185,35 @@ def _show(name): toml_config = config.get_config(True) if name is not None: - value = toml_config.get(name) - if value is None: + file_value = toml_config.get(name) + try: + # If the user presented a partial key name, eg 'core' when + # we have 'core.xyz'; we will get an exception here + effective_value, envvar_name = config.get_config_val_envvar(name) + except DCOSException as e: + # The expected case of a partial key name has special + # handling via this mechanism. + if isinstance(file_value, collections.Mapping): + exc_msg = config.generate_choice_msg(name, file_value) + raise DCOSException(exc_msg) + raise # Unexpected errors, pass right along + + if effective_value is None: raise DCOSException("Property {!r} doesn't exist".format(name)) - elif isinstance(value, collections.Mapping): - raise DCOSException(config.generate_choice_msg(name, value)) else: - emitter.publish(value) + msg = _format_config(file_value, effective_value, + envvar_name=envvar_name) + emitter.publish(msg) + else: # Let's list all of the values for key, value in sorted(toml_config.property_items()): - if key == "core.dcos_acs_token": - value = "*"*8 - emitter.publish('{} {}'.format(key, value)) + file_value = toml_config.get(key) + effective_value, envvar_name = config.get_config_val_envvar(key) + + msg = _format_config(file_value, effective_value, key, + envvar_name=envvar_name) + emitter.publish(msg) return 0 diff --git a/dcos/config.py b/dcos/config.py index e080cb7bb..2e619c9fb 100644 --- a/dcos/config.py +++ b/dcos/config.py @@ -55,9 +55,13 @@ def get_config(mutable=False): return load_from_path(path, mutable) -def get_config_val(name, config=None): - """Returns the config value for the specified key. Looks for corresponding - environment variable first, and if it doesn't exist, uses the config value. +def get_config_val_envvar(name, config=None): + """Returns a tuple of the config value for the specified key and + the name of any environment variable which overwrote the value + from the configuration. If no environment variable applies, None + is returned for the environment variable name. + Looks for corresponding environment variable first, and if it + doesn't exist, uses the config value. - "core" properties get resolved to env variable DCOS_SUBKEY. With the exception of subkeys that already start with DCOS, in which case we look for SUBKEY first, and "DCOS_SUBKEY" second, and finally the config value. @@ -68,7 +72,7 @@ def get_config_val(name, config=None): :param config: config :type config: Toml :returns: value of 'name' parameter - :rtype: str | None + :rtype: (str | None, str | None) """ if config is None: @@ -85,7 +89,26 @@ def get_config_val(name, config=None): else: env_var = "DCOS_{}_{}".format(section, subkey) - return os.environ.get(env_var) or config.get(name) + return os.environ.get(env_var) or config.get(name), env_var or None + + +def get_config_val(name, config=None): + """Returns the config value for the specified key. Looks for corresponding + environment variable first, and if it doesn't exist, uses the config value. + - "core" properties get resolved to env variable DCOS_SUBKEY. With the + exception of subkeys that already start with DCOS, in which case we look + for SUBKEY first, and "DCOS_SUBKEY" second, and finally the config value. + - everything else gets resolved to DCOS_SECTION_SUBKEY + + :param name: name of paramater + :type name: str + :param config: config + :type config: Toml + :returns: value of 'name' parameter + :rtype: str | None + """ + val, _ = get_config_val_envvar(name, config=None) + return val def missing_config_exception(keys):