1# -*- coding: utf-8 -*-
2# Copyright: (c) 2017, Ansible Project
3# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
4
5# Make coding more python3-ish
6from __future__ import (absolute_import, division, print_function)
7__metaclass__ = type
8
9import os
10import os.path
11import pytest
12
13from ansible.config.manager import ConfigManager, Setting, ensure_type, resolve_path, get_config_type
14from ansible.errors import AnsibleOptionsError, AnsibleError
15from ansible.module_utils.six import integer_types, string_types
16from ansible.parsing.yaml.objects import AnsibleVaultEncryptedUnicode
17
18curdir = os.path.dirname(__file__)
19cfg_file = os.path.join(curdir, 'test.cfg')
20cfg_file2 = os.path.join(curdir, 'test2.cfg')
21
22expected_ini = {'CONFIG_FILE': Setting(name='CONFIG_FILE', value=cfg_file, origin='', type='string'),
23                'config_entry': Setting(name='config_entry', value=u'fromini', origin=cfg_file, type='string'),
24                'config_entry_bool': Setting(name='config_entry_bool', value=False, origin=cfg_file, type='bool'),
25                'config_entry_list': Setting(name='config_entry_list', value=['fromini'], origin=cfg_file, type='list'),
26                'config_entry_deprecated': Setting(name='config_entry_deprecated', value=u'fromini', origin=cfg_file, type='string'),
27                'config_entry_multi': Setting(name='config_entry_multi', value=u'morefromini', origin=cfg_file, type='string'),
28                'config_entry_multi_deprecated': Setting(name='config_entry_multi_deprecated', value=u'morefromini', origin=cfg_file, type='string'),
29                'config_entry_multi_deprecated_source': Setting(name='config_entry_multi_deprecated_source', value=u'morefromini',
30                                                                origin=cfg_file, type='string')}
31
32ensure_test_data = [
33    ('a,b', 'list', list),
34    (['a', 'b'], 'list', list),
35    ('y', 'bool', bool),
36    ('yes', 'bool', bool),
37    ('on', 'bool', bool),
38    ('1', 'bool', bool),
39    ('true', 'bool', bool),
40    ('t', 'bool', bool),
41    (1, 'bool', bool),
42    (1.0, 'bool', bool),
43    (True, 'bool', bool),
44    ('n', 'bool', bool),
45    ('no', 'bool', bool),
46    ('off', 'bool', bool),
47    ('0', 'bool', bool),
48    ('false', 'bool', bool),
49    ('f', 'bool', bool),
50    (0, 'bool', bool),
51    (0.0, 'bool', bool),
52    (False, 'bool', bool),
53    ('10', 'int', integer_types),
54    (20, 'int', integer_types),
55    ('0.10', 'float', float),
56    (0.2, 'float', float),
57    ('/tmp/test.yml', 'pathspec', list),
58    ('/tmp/test.yml,/home/test2.yml', 'pathlist', list),
59    ('a', 'str', string_types),
60    ('a', 'string', string_types),
61    ('Café', 'string', string_types),
62    ('', 'string', string_types),
63    ('None', 'none', type(None))
64]
65
66
67class TestConfigManager:
68    @classmethod
69    def setup_class(cls):
70        cls.manager = ConfigManager(cfg_file, os.path.join(curdir, 'test.yml'))
71
72    @classmethod
73    def teardown_class(cls):
74        cls.manager = None
75
76    def test_initial_load(self):
77        assert self.manager.data._global_settings == expected_ini
78
79    @pytest.mark.parametrize("value, expected_type, python_type", ensure_test_data)
80    def test_ensure_type(self, value, expected_type, python_type):
81        assert isinstance(ensure_type(value, expected_type), python_type)
82
83    def test_resolve_path(self):
84        assert os.path.join(curdir, 'test.yml') == resolve_path('./test.yml', cfg_file)
85
86    def test_resolve_path_cwd(self):
87        assert os.path.join(os.getcwd(), 'test.yml') == resolve_path('{{CWD}}/test.yml')
88        assert os.path.join(os.getcwd(), 'test.yml') == resolve_path('./test.yml')
89
90    def test_value_and_origin_from_ini(self):
91        assert self.manager.get_config_value_and_origin('config_entry') == ('fromini', cfg_file)
92
93    def test_value_from_ini(self):
94        assert self.manager.get_config_value('config_entry') == 'fromini'
95
96    def test_value_and_origin_from_alt_ini(self):
97        assert self.manager.get_config_value_and_origin('config_entry', cfile=cfg_file2) == ('fromini2', cfg_file2)
98
99    def test_value_from_alt_ini(self):
100        assert self.manager.get_config_value('config_entry', cfile=cfg_file2) == 'fromini2'
101
102    def test_config_types(self):
103        assert get_config_type('/tmp/ansible.ini') == 'ini'
104        assert get_config_type('/tmp/ansible.cfg') == 'ini'
105        assert get_config_type('/tmp/ansible.yaml') == 'yaml'
106        assert get_config_type('/tmp/ansible.yml') == 'yaml'
107
108    def test_config_types_negative(self):
109        with pytest.raises(AnsibleOptionsError) as exec_info:
110            get_config_type('/tmp/ansible.txt')
111        assert "Unsupported configuration file extension for" in str(exec_info.value)
112
113    def test_read_config_yaml_file(self):
114        assert isinstance(self.manager._read_config_yaml_file(os.path.join(curdir, 'test.yml')), dict)
115
116    def test_read_config_yaml_file_negative(self):
117        with pytest.raises(AnsibleError) as exec_info:
118            self.manager._read_config_yaml_file(os.path.join(curdir, 'test_non_existent.yml'))
119
120        assert "Missing base YAML definition file (bad install?)" in str(exec_info.value)
121
122    def test_entry_as_vault_var(self):
123        class MockVault:
124
125            def decrypt(self, value):
126                return value
127
128        vault_var = AnsibleVaultEncryptedUnicode(b"vault text")
129        vault_var.vault = MockVault()
130
131        actual_value, actual_origin = self.manager._loop_entries({'name': vault_var}, [{'name': 'name'}])
132        assert actual_value == "vault text"
133        assert actual_origin == "name"
134
135    @pytest.mark.parametrize("value_type", ("str", "string", None))
136    def test_ensure_type_with_vaulted_str(self, value_type):
137        class MockVault:
138            def decrypt(self, value):
139                return value
140
141        vault_var = AnsibleVaultEncryptedUnicode(b"vault text")
142        vault_var.vault = MockVault()
143
144        actual_value = ensure_type(vault_var, value_type)
145        assert actual_value == "vault text"
146