diff --git a/cli/dcoscli/auth/main.py b/cli/dcoscli/auth/main.py index ca5ea0f..988248e 100644 --- a/cli/dcoscli/auth/main.py +++ b/cli/dcoscli/auth/main.py @@ -75,7 +75,7 @@ def _login(): # every call to login will generate a new token if applicable _logout() - conf = util.get_config() + conf = config.get_config() dcos_url = conf.get("core.dcos_url") if dcos_url is None: msg = ("Please provide the url to your DCOS cluster: " @@ -103,6 +103,6 @@ def _logout(): :rtype: int """ - if util.get_config().get("core.dcos_acs_token") is not None: + if config.get_config().get("core.dcos_acs_token") is not None: config.unset("core.dcos_acs_token") return 0 diff --git a/cli/dcoscli/config/main.py b/cli/dcoscli/config/main.py index 06c502d..ba92767 100644 --- a/cli/dcoscli/config/main.py +++ b/cli/dcoscli/config/main.py @@ -3,7 +3,7 @@ import collections import dcoscli import docopt from dcos import cmds, config, emitting, http, util -from dcos.errors import DCOSException +from dcos.errors import DCOSException, DefaultError from dcoscli.subcommand import default_command_info, default_doc from dcoscli.util import decorate_docopt_usage @@ -91,7 +91,8 @@ def _set(name, value): notice = "This config property has been deprecated." return DCOSException(notice) - config.set_val(name, value) + toml, msg = config.set_val(name, value) + emitter.publish(DefaultError(msg)) return 0 @@ -102,7 +103,9 @@ def _unset(name): :rtype: int """ - config.unset(name) + msg = config.unset(name) + emitter.publish(DefaultError(msg)) + return 0 @@ -112,7 +115,7 @@ def _show(name): :rtype: int """ - toml_config = util.get_config(True) + toml_config = config.get_config(True) if name is not None: value = toml_config.get(name) @@ -138,7 +141,7 @@ def _validate(): :rtype: int """ - toml_config = util.get_config(True) + toml_config = config.get_config(True) errs = util.validate_json(toml_config._dictionary, config.generate_root_schema(toml_config)) diff --git a/cli/dcoscli/main.py b/cli/dcoscli/main.py index 01c68f5..b112d59 100644 --- a/cli/dcoscli/main.py +++ b/cli/dcoscli/main.py @@ -5,7 +5,7 @@ import sys import dcoscli import docopt import six -from dcos import constants, emitting, errors, http, subcommand, util +from dcos import config, constants, emitting, errors, http, subcommand, util from dcos.errors import DCOSException from dcoscli.subcommand import SubcommandMain, default_doc @@ -57,8 +57,8 @@ def _main(): signal.signal(signal.SIGINT, signal_handler) http.silence_requests_warnings() - config = util.get_config() - set_ssl_info_env_vars(config) + toml_config = config.get_config() + set_ssl_info_env_vars(toml_config) args = docopt.docopt(default_doc("dcos"), options_first=True) @@ -72,7 +72,7 @@ def _main(): util.configure_process_from_environ() if args['--version']: - return _get_versions(config.get("core.dcos_url")) + return _get_versions(toml_config.get("core.dcos_url")) command = args[''] @@ -116,20 +116,20 @@ def signal_handler(signal, frame): sys.exit(0) -def set_ssl_info_env_vars(config): - """Set SSL info from config to environment variable if enviornment +def set_ssl_info_env_vars(toml_config): + """Set SSL info from toml_config to environment variable if enviornment variable doesn't exist - :param config: config - :type config: Toml + :param toml_config: config + :type toml_config: Toml :rtype: None """ - if 'core.ssl_verify' in config and ( + if 'core.ssl_verify' in toml_config and ( not os.environ.get(constants.DCOS_SSL_VERIFY_ENV)): os.environ[constants.DCOS_SSL_VERIFY_ENV] = six.text_type( - config['core.ssl_verify']) + toml_config['core.ssl_verify']) if __name__ == "__main__": main() diff --git a/cli/dcoscli/package/main.py b/cli/dcoscli/package/main.py index 8cc4ce4..788a186 100644 --- a/cli/dcoscli/package/main.py +++ b/cli/dcoscli/package/main.py @@ -5,8 +5,8 @@ import sys import dcoscli import docopt import pkg_resources -from dcos import (cmds, cosmospackage, emitting, http, options, package, - subcommand, util) +from dcos import (cmds, config, cosmospackage, emitting, http, options, + package, subcommand, util) from dcos.errors import DCOSException from dcoscli import tables from dcoscli.subcommand import default_command_info, default_doc @@ -524,10 +524,10 @@ def _get_cosmos_url(): :returns: cosmos base url :rtype: str """ - config = util.get_config() - cosmos_url = config.get("package.cosmos_url") + toml_config = config.get_config() + cosmos_url = toml_config.get("package.cosmos_url") if cosmos_url is None: - cosmos_url = util.get_config_vals(['core.dcos_url'], config)[0] + cosmos_url = config.get_config_vals(['core.dcos_url'], toml_config)[0] return cosmos_url diff --git a/cli/tests/integrations/common.py b/cli/tests/integrations/common.py index 656bbac..334d7af 100644 --- a/cli/tests/integrations/common.py +++ b/cli/tests/integrations/common.py @@ -7,7 +7,7 @@ import subprocess import time import six -from dcos import http, util +from dcos import config, http from six.moves import urllib @@ -321,7 +321,7 @@ def delete_zk_node(znode): :rtype: None """ - dcos_url = util.get_config_vals(['core.dcos_url'])[0] + dcos_url = config.get_config_vals(['core.dcos_url'])[0] znode_url = urllib.parse.urljoin( dcos_url, '/exhibitor/exhibitor/v1/explorer/znode/{}'.format(znode)) diff --git a/cli/tests/integrations/test_auth.py b/cli/tests/integrations/test_auth.py index 743932f..63dfad1 100644 --- a/cli/tests/integrations/test_auth.py +++ b/cli/tests/integrations/test_auth.py @@ -46,7 +46,5 @@ def test_logout_with_token(env): stderr=stderr, env=env) - stderr = b'Removed [core.dcos_acs_token]\n' assert_command(['dcos', 'auth', 'logout'], - stderr=stderr, env=env) diff --git a/cli/tests/unit/test_task.py b/cli/tests/unit/test_task.py index a03b50d..5efb635 100644 --- a/cli/tests/unit/test_task.py +++ b/cli/tests/unit/test_task.py @@ -9,7 +9,7 @@ from mock import MagicMock, patch from .common import assert_mock -@patch('dcos.util.get_config') +@patch('dcos.config.get_config') def test_log_master_unavailable(config_mock): config_mock.return_value = {'core.dcos_url': 'foo'} diff --git a/dcos/config.py b/dcos/config.py index 8e60fc0..0f5990e 100644 --- a/dcos/config.py +++ b/dcos/config.py @@ -1,28 +1,104 @@ import collections import copy import json +import os import pkg_resources import toml -from dcos import emitting, jsonitem, subcommand, util -from dcos.errors import DCOSException, DefaultError - -emitter = emitting.FlatEmitter() +from dcos import constants, jsonitem, subcommand, util +from dcos.errors import DCOSException logger = util.get_logger(__name__) +def get_config_path(): + """ Returns the path to the DCOS config file. + + :returns: path to the DCOS config file + :rtype: str + """ + + return os.environ.get(constants.DCOS_CONFIG_ENV, get_default_config_path()) + + +def get_default_config_path(): + """Returns the default path to the DCOS config file. + + :returns: path to the DCOS config file + :rtype: str + """ + + return os.path.expanduser( + os.path.join("~", + constants.DCOS_DIR, + 'dcos.toml')) + + +def get_config(mutable=False): + """Returns the DCOS configuration object and creates config file is none + found and `DCOS_CONFIG` set to default value + + :param mutable: True if the returned Toml object should be mutable + :type mutable: boolean + :returns: Configuration object + :rtype: Toml | MutableToml + """ + + path = get_config_path() + default = get_default_config_path() + + if path == default: + util.ensure_dir_exists(os.path.dirname(default)) + return load_from_path(path, mutable) + + +def get_config_vals(keys, config=None): + """Gets config values for each of the keys. Raises a DCOSException if + any of the keys don't exist. + + :param config: config + :type config: Toml + :param keys: keys in the config dict + :type keys: [str] + :returns: values for each of the keys + :rtype: [object] + """ + + config = config or get_config() + missing = [key for key in keys if key not in config] + if missing: + raise missing_config_exception(keys) + + return [config[key] for key in keys] + + +def missing_config_exception(keys): + """ DCOSException for a missing config value + + :param keys: keys in the config dict + :type keys: [str] + :returns: DCOSException + :rtype: DCOSException + """ + + msg = '\n'.join( + 'Missing required config parameter: "{0}".'.format(key) + + ' Please run `dcos config set {0} `.'.format(key) + for key in keys) + return DCOSException(msg) + + def set_val(name, value): """ :param name: name of paramater :type name: str :param value: value to set to paramater `name` :type param: str - :returns: Toml config - :rtype: Toml + :returns: Toml config, message of change + :rtype: Toml, str """ - toml_config = util.get_config(True) + toml_config = get_config(True) section, subkey = split_key(name) @@ -57,9 +133,8 @@ def set_val(name, value): msg += "already set to '{}'".format(old_value) else: msg += "changed from '{}' to '{}'".format(old_value, new_value) - emitter.publish(DefaultError(msg)) - return toml_config + return toml_config, msg def load_from_path(path, mutable=False): @@ -90,7 +165,7 @@ def save(toml_config): """ serial = toml.dumps(toml_config._dictionary) - path = util.get_config_path() + path = get_config_path() with util.open_file(path, 'w') as config_file: config_file.write(serial) @@ -115,11 +190,11 @@ def unset(name): """ :param name: name of config value to unset :type name: str - :returns: process status - :rtype: None + :returns: message of property removed + :rtype: str """ - toml_config = util.get_config(True) + toml_config = get_config(True) toml_config_pre = copy.deepcopy(toml_config) section = name.split(".", 1)[0] if section not in toml_config_pre._dictionary: @@ -130,9 +205,8 @@ def unset(name): elif isinstance(value, collections.Mapping): raise DCOSException(_generate_choice_msg(name, value)) else: - emitter.publish(DefaultError("Removed [{}]".format(name))) save(toml_config) - return + return "Removed [{}]".format(name) def _generate_choice_msg(name, value): diff --git a/dcos/emitting.py b/dcos/emitting.py index 6e4e9be..a9f22bf 100644 --- a/dcos/emitting.py +++ b/dcos/emitting.py @@ -11,7 +11,7 @@ import sys import pager import pygments import six -from dcos import constants, errors, util +from dcos import config, constants, errors, util from pygments.formatters import Terminal256Formatter from pygments.lexers import JsonLexer @@ -172,7 +172,7 @@ def _page(output, pager_command=None): num_lines = output.count('\n') exceeds_tty_height = pager.getheight() - 1 < num_lines - paginate = util.get_config().get("core.pagination", True) + paginate = config.get_config().get("core.pagination", True) if exceeds_tty_height and paginate: pydoc.pipepager(output, cmd=pager_command) else: diff --git a/dcos/http.py b/dcos/http.py index 47531fd..f24693d 100644 --- a/dcos/http.py +++ b/dcos/http.py @@ -172,7 +172,7 @@ def _request_with_auth(response, elif response.status_code == 401 and \ auth_scheme in ["acsjwt", "oauthjwt"]: - if util.get_config().get("core.dcos_acs_token") is not None: + if config.get_config().get("core.dcos_acs_token") is not None: msg = ("Your core.dcos_acs_token is invalid. " "Please run: `dcos auth login`") raise DCOSException(msg) @@ -472,7 +472,7 @@ def _get_dcos_auth(auth_scheme, username, password, hostname): :rtype: AuthBase """ - toml_config = util.get_config() + toml_config = config.get_config() token = toml_config.get("core.dcos_acs_token") if token is None: dcos_url = toml_config.get("core.dcos_url") diff --git a/dcos/marathon.py b/dcos/marathon.py index 9785063..40a769d 100644 --- a/dcos/marathon.py +++ b/dcos/marathon.py @@ -1,7 +1,7 @@ import json from distutils.version import LooseVersion -from dcos import http, util +from dcos import config, http, util from dcos.errors import DCOSException, DCOSHTTPException from six.moves import urllib @@ -9,36 +9,36 @@ from six.moves import urllib logger = util.get_logger(__name__) -def create_client(config=None): +def create_client(toml_config=None): """Creates a Marathon client with the supplied configuration. - :param config: configuration dictionary - :type config: config.Toml + :param toml_config: configuration dictionary + :type toml_config: config.Toml :returns: Marathon client :rtype: dcos.marathon.Client """ - if config is None: - config = util.get_config() + if toml_config is None: + toml_config = config.get_config() - marathon_url = _get_marathon_url(config) - timeout = config.get('core.timeout', http.DEFAULT_TIMEOUT) + marathon_url = _get_marathon_url(toml_config) + timeout = toml_config.get('core.timeout', http.DEFAULT_TIMEOUT) logger.info('Creating marathon client with: %r', marathon_url) return Client(marathon_url, timeout=timeout) -def _get_marathon_url(config): +def _get_marathon_url(toml_config): """ - :param config: configuration dictionary - :type config: config.Toml + :param toml_config: configuration dictionary + :type toml_config: config.Toml :returns: marathon base url :rtype: str """ - marathon_url = config.get('marathon.url') + marathon_url = toml_config.get('marathon.url') if marathon_url is None: - dcos_url = util.get_config_vals(['core.dcos_url'], config)[0] + dcos_url = config.get_config_vals(['core.dcos_url'], toml_config)[0] marathon_url = urllib.parse.urljoin(dcos_url, 'service/marathon/') return marathon_url diff --git a/dcos/mesos.py b/dcos/mesos.py index 2f6ed54..23c3948 100644 --- a/dcos/mesos.py +++ b/dcos/mesos.py @@ -2,7 +2,7 @@ import fnmatch import itertools import os -from dcos import http, util +from dcos import config, http, util from dcos.errors import DCOSException, DCOSHTTPException from six.moves import urllib @@ -29,14 +29,14 @@ class DCOSClient(object): """Client for communicating with DCOS""" def __init__(self): - config = util.get_config() + toml_config = config.get_config() - self._dcos_url = config.get("core.dcos_url") + self._dcos_url = toml_config.get("core.dcos_url") if self._dcos_url is None: - raise util.missing_config_exception(['core.dcos_url']) - self._mesos_master_url = config.get('core.mesos_master_url') + raise config.missing_config_exception(['core.dcos_url']) + self._mesos_master_url = toml_config.get('core.mesos_master_url') - self._timeout = config.get('core.timeout') + self._timeout = toml_config.get('core.timeout') def get_dcos_url(self, path): """ Create a DCOS URL @@ -269,7 +269,7 @@ class MesosDNSClient(object): """ def __init__(self, url=None): self.url = url or urllib.parse.urljoin( - util.get_config_vals(['core.dcos_url'])[0], '/mesos_dns/') + config.get_config_vals(['core.dcos_url'])[0], '/mesos_dns/') def _path(self, path): """ Construct a full path diff --git a/dcos/subcommand.py b/dcos/subcommand.py index be82afe..4639512 100644 --- a/dcos/subcommand.py +++ b/dcos/subcommand.py @@ -12,11 +12,10 @@ import zipfile from subprocess import PIPE, Popen import requests -from dcos import constants, emitting, util +from dcos import constants, util from dcos.errors import DCOSException logger = util.get_logger(__name__) -emitter = emitting.FlatEmitter() def command_executables(subcommand): diff --git a/dcos/util.py b/dcos/util.py index faa89eb..f96e859 100644 --- a/dcos/util.py +++ b/dcos/util.py @@ -165,86 +165,6 @@ def read_file(path): return file_.read() -def get_config_path(): - """ Returns the path to the DCOS config file. - - :returns: path to the DCOS config file - :rtype: str - """ - - return os.environ.get(constants.DCOS_CONFIG_ENV, get_default_config_path()) - - -def get_default_config_path(): - """Returns the default path to the DCOS config file. - - :returns: path to the DCOS config file - :rtype: str - """ - - return os.path.expanduser( - os.path.join("~", - constants.DCOS_DIR, - 'dcos.toml')) - - -def get_config(mutable=False): - """Returns the DCOS configuration object and creates config file is none - found and `DCOS_CONFIG` set to default value - - :param mutable: True if the returned Toml object should be mutable - :type mutable: boolean - :returns: Configuration object - :rtype: Toml | MutableToml - """ - - # avoid circular import - from dcos import config - - path = get_config_path() - default = get_default_config_path() - - if path == default: - ensure_dir_exists(os.path.dirname(default)) - return config.load_from_path(path, mutable) - - -def get_config_vals(keys, config=None): - """Gets config values for each of the keys. Raises a DCOSException if - any of the keys don't exist. - - :param config: config - :type config: Toml - :param keys: keys in the config dict - :type keys: [str] - :returns: values for each of the keys - :rtype: [object] - """ - - config = config or get_config() - missing = [key for key in keys if key not in config] - if missing: - raise missing_config_exception(keys) - - return [config[key] for key in keys] - - -def missing_config_exception(keys): - """ DCOSException for a missing config value - - :param keys: keys in the config dict - :type keys: [str] - :returns: DCOSException - :rtype: DCOSException - """ - - msg = '\n'.join( - 'Missing required config parameter: "{0}".'.format(key) + - ' Please run `dcos config set {0} `.'.format(key) - for key in keys) - return DCOSException(msg) - - def which(program): """Returns the path to the named executable program.