def test_debug_print(monkeypatch, capsys): delimiter = '-' * 78 + '\n' import fabric.state monkeypatch.setitem(fabric.state.output, 'debug', False) debug_print("test") out, err = capsys.readouterr() assert out == "" assert err == "" monkeypatch.setitem(fabric.state.output, 'debug', True) debug_print("test") out, err = capsys.readouterr() assert out == "test\n" + delimiter assert err == "" monkeypatch.setitem(fabric.state.output, 'debug', True) debug_print("test\n") out, err = capsys.readouterr() assert out == "test\n" + delimiter assert err == "" monkeypatch.setitem(fabric.state.output, 'debug', True) debug_print(["foo", "bar"]) out, err = capsys.readouterr() assert out == "['foo', 'bar']\n" + delimiter assert err == ""
def read_config(config_filename=None): # pylint: disable=too-many-locals,too-many-branches,too-many-statements r"""Read configuration from .yaml file. If ``config_filename`` is None - :func:`~read_config` will try to use ``config_filename`` constructed from ``env.real_fabfile`` by replacing ``.py`` file extension with ``.yaml`` one. If such config file exists it will be loaded and parsed by :func:`~read_config`. .. note:: :func:`~read_config` with argument ``None`` is automatically executed during loading module ``fabrix.api``. Nevertheless, :func:`~read_config` can be called again from fabfile function to load specific configuration, for example, ``read_config('stage.yaml')`` or ``read_config('prod.yaml')`` - in this case files 'stage.yaml' and 'prod.yaml' will be looked for in the directory where ``env.real_fabfile`` is located. Configuration file has `yaml format <https://en.wikipedia.org/wiki/YAML#Syntax>`_ and can contains: - ``hosts:`` is list of hosts. Each host can be ip address or hostname. Also username and port can be mentioned. For example: * 10.10.10.10 * example.com * root\@example.com * example.com:22 * root\@example.com:22 - ``roles:`` is dictionary with two possible keys, ``role`` and ``hosts``. * ``role`` is string, role name. It will be used as role name in Fabric ``env.roledefs`` dictionary. * ``hosts`` is list of hosts of this role, with syntax as it described above. - ``host_vars`` is dictionary with two possible keys, ``host`` and ``vars``. * ``host`` is existing host string from global ``hosts`` or ``roles`` ``hosts`` lists. * ``vars`` is dictionary, where keys is variable names and values can be any type. - ``role_vars`` is dictionary with two possible keys, ``role`` and ``vars``. * ``role`` is existing role name from global ``roles`` dictionary. * ``vars`` is dictionary, where keys is variable names and values can be any type. - ``defaults`` is dictionary where keys is variable names and values can be any type. - ``local_vars`` is dictionary where keys is variable names and values can be any type. Configuration file has additional restrictions: - ``hosts`` and ``roles`` can't be simultaneously defined in config, these two options are mutually exclusive. - one of ``hosts`` and ``roles`` must be defined in config, else config vill be considered invalid and rejected. - ``host_vars`` can be defined even in case when ``roles`` defined and global ``hosts`` is not defined. - ``role_vars`` can be defined in config only if ``roles`` defined. Variables from ``defaults`` dictionary has lowest priority and can be overriden via ``role_vars`` or ``host_vars``. If some variable defined in ``defaults`` and in ``role_vars`` - ``role_vars`` definition has higher priority for hosts with this specific role. Variable definition in ``host_vars`` has highest priority and override any variables defined in ``defaults`` and ``role_vars`` for this specific host. ``local_vars`` is variables intended to use as local configuration for host where fabfile functions is executed, these local variables are acessible via global :obj:`~fabrix.config.local_conf` dictionary after importing :obj:`~fabrix.config.local_conf` from ``fabrix.api``. ``defaults``/``role_vars``/``host_vars`` can be acessible via global :obj:`~fabrix.config.conf` dictionary after importing :obj:`~fabrix.config.conf` from ``fabrix.api``. Args: config_filename: full/relative configuration file name or None. Returns: After successful execution, :func:`~read_config` changes ``env.hosts`` list, ``env.roledefs`` dictionary and set global variables :obj:`~fabrix.config.conf` and :obj:`~fabrix.config.local_conf`. Raises: :class:`~exceptions.SystemExit`: When error occurred during parsing of configuration file. """ argument_config_filename = config_filename if env.real_fabfile is None: return if argument_config_filename is None: dirname = os.path.dirname(env.real_fabfile) basename = os.path.basename(env.real_fabfile) name, dummy_ext = os.path.splitext(basename) config_filename = os.path.join(dirname, name + '.yaml') elif not os.path.isabs(argument_config_filename): dirname = os.path.dirname(env.real_fabfile) config_filename = os.path.join(dirname, argument_config_filename) else: config_filename = argument_config_filename if not os.path.isfile(config_filename): if argument_config_filename is None: return else: abort('read_config: config \'%s\' not exists' % config_filename) else: debug_print('fabrix: using config \'%s\'' % config_filename) try: with open(config_filename) as config_file: config = yaml.load(config_file) except yaml.parser.ParserError, ex: abort('read_config: error parsing config \'%s\':\n\n%s' % (config_filename, ex))
config_filename = argument_config_filename if not os.path.isfile(config_filename): if argument_config_filename is None: return else: abort('read_config: config \'%s\' not exists' % config_filename) else: debug_print('fabrix: using config \'%s\'' % config_filename) try: with open(config_filename) as config_file: config = yaml.load(config_file) except yaml.parser.ParserError, ex: abort('read_config: error parsing config \'%s\':\n\n%s' % (config_filename, ex)) config_text = read_local_file(config_filename) config_yaml = yaml.dump(config) debug_print('config_text:', config_text, 'config_yaml:', config_yaml, 'config:', config) if 'hosts' in config and 'roles' in config: abort('read_config: hosts and roles can\'t be simultaneously defined in config') if 'hosts' not in config and 'roles' not in config: abort('read_config: hosts or roles must be defined in config') hosts = list() if 'hosts' in config: if not isinstance(config['hosts'], list): abort('read_config: hosts must be list type') if not config['hosts']: abort('read_config: hosts must not be empty') hosts_set = set() for host in config['hosts']: if host is None: abort('read_config: hosts host can\'t be empty string') if not isinstance(host, basestring):