1"""
2Unit tests for salt.config
3"""
4import logging
5import os
6import textwrap
7
8import pytest
9import salt.config
10import salt.minion
11import salt.syspaths
12import salt.utils.files
13import salt.utils.network
14import salt.utils.platform
15import salt.utils.yaml
16from salt.exceptions import (
17    CommandExecutionError,
18    SaltCloudConfigError,
19    SaltConfigurationError,
20)
21from salt.syspaths import CONFIG_DIR
22from tests.support.helpers import patched_environ, with_tempdir, with_tempfile
23from tests.support.mixins import AdaptedConfigurationTestCaseMixin
24from tests.support.mock import MagicMock, Mock, patch
25from tests.support.runtests import RUNTIME_VARS
26from tests.support.unit import TestCase, skipIf
27
28log = logging.getLogger(__name__)
29
30SAMPLE_CONF_DIR = os.path.join(RUNTIME_VARS.CODE_DIR, "conf") + os.sep
31
32# mock hostname should be more complex than the systems FQDN
33MOCK_HOSTNAME = "very.long.complex.fqdn.that.is.crazy.extra.long.example.com"
34
35MOCK_ETC_HOSTS = textwrap.dedent(
36    """\
37    ##
38    # Host Database
39    #
40    # localhost is used to configure the loopback interface
41    # when the system is booting.  Do not change this entry.
42    ## The empty line below must remain, it factors into the tests.
43
44    127.0.0.1      localhost   {hostname}
45    10.0.0.100     {hostname}
46    200.200.200.2  other.host.alias.com
47    ::1            ip6-localhost ip6-loopback
48    fe00::0        ip6-localnet
49    ff00::0        ip6-mcastprefix
50    """.format(
51        hostname=MOCK_HOSTNAME
52    )
53)
54MOCK_ETC_HOSTNAME = "{}\n".format(MOCK_HOSTNAME)
55PATH = "path/to/some/cloud/conf/file"
56DEFAULT = {"default_include": PATH}
57
58
59class DefaultConfigsBase:
60    @classmethod
61    def setUpClass(cls):
62        cls.mock_master_default_opts = dict(
63            root_dir=RUNTIME_VARS.TMP_ROOT_DIR,
64            log_file=os.path.join(
65                RUNTIME_VARS.TMP_ROOT_DIR, "var", "log", "salt", "master"
66            ),
67            pid_file=os.path.join(
68                RUNTIME_VARS.TMP_ROOT_DIR, "var", "run", "salt-master.pid"
69            ),
70        )
71
72
73class SampleConfTest(DefaultConfigsBase, TestCase):
74    """
75    Validate files in the salt/conf directory.
76    """
77
78    def test_conf_master_sample_is_commented(self):
79        """
80        The sample config file located in salt/conf/master must be completely
81        commented out. This test checks for any lines that are not commented or blank.
82        """
83        master_config = SAMPLE_CONF_DIR + "master"
84        ret = salt.config._read_conf_file(master_config)
85        self.assertEqual(
86            ret,
87            {},
88            "Sample config file '{}' must be commented out.".format(master_config),
89        )
90
91    def test_conf_minion_sample_is_commented(self):
92        """
93        The sample config file located in salt/conf/minion must be completely
94        commented out. This test checks for any lines that are not commented or blank.
95        """
96        minion_config = SAMPLE_CONF_DIR + "minion"
97        ret = salt.config._read_conf_file(minion_config)
98        self.assertEqual(
99            ret,
100            {},
101            "Sample config file '{}' must be commented out.".format(minion_config),
102        )
103
104    def test_conf_cloud_sample_is_commented(self):
105        """
106        The sample config file located in salt/conf/cloud must be completely
107        commented out. This test checks for any lines that are not commented or blank.
108        """
109        cloud_config = SAMPLE_CONF_DIR + "cloud"
110        ret = salt.config._read_conf_file(cloud_config)
111        self.assertEqual(
112            ret,
113            {},
114            "Sample config file '{}' must be commented out.".format(cloud_config),
115        )
116
117    def test_conf_cloud_profiles_sample_is_commented(self):
118        """
119        The sample config file located in salt/conf/cloud.profiles must be completely
120        commented out. This test checks for any lines that are not commented or blank.
121        """
122        cloud_profiles_config = SAMPLE_CONF_DIR + "cloud.profiles"
123        ret = salt.config._read_conf_file(cloud_profiles_config)
124        self.assertEqual(
125            ret,
126            {},
127            "Sample config file '{}' must be commented out.".format(
128                cloud_profiles_config
129            ),
130        )
131
132    def test_conf_cloud_providers_sample_is_commented(self):
133        """
134        The sample config file located in salt/conf/cloud.providers must be completely
135        commented out. This test checks for any lines that are not commented or blank.
136        """
137        cloud_providers_config = SAMPLE_CONF_DIR + "cloud.providers"
138        ret = salt.config._read_conf_file(cloud_providers_config)
139        self.assertEqual(
140            ret,
141            {},
142            "Sample config file '{}' must be commented out.".format(
143                cloud_providers_config
144            ),
145        )
146
147    def test_conf_proxy_sample_is_commented(self):
148        """
149        The sample config file located in salt/conf/proxy must be completely
150        commented out. This test checks for any lines that are not commented or blank.
151        """
152        proxy_config = SAMPLE_CONF_DIR + "proxy"
153        ret = salt.config._read_conf_file(proxy_config)
154        self.assertEqual(
155            ret,
156            {},
157            "Sample config file '{}' must be commented out.".format(proxy_config),
158        )
159
160    def test_conf_roster_sample_is_commented(self):
161        """
162        The sample config file located in salt/conf/roster must be completely
163        commented out. This test checks for any lines that are not commented or blank.
164        """
165        roster_config = SAMPLE_CONF_DIR + "roster"
166        ret = salt.config._read_conf_file(roster_config)
167        self.assertEqual(
168            ret,
169            {},
170            "Sample config file '{}' must be commented out.".format(roster_config),
171        )
172
173    def test_conf_cloud_profiles_d_files_are_commented(self):
174        """
175        All cloud profile sample configs in salt/conf/cloud.profiles.d/* must be completely
176        commented out. This test loops through all of the files in that directory to check
177        for any lines that are not commented or blank.
178        """
179        cloud_sample_dir = SAMPLE_CONF_DIR + "cloud.profiles.d/"
180        if not os.path.exists(cloud_sample_dir):
181            self.skipTest(
182                "Sample config directory '{}' is missing.".format(cloud_sample_dir)
183            )
184        cloud_sample_files = os.listdir(cloud_sample_dir)
185        for conf_file in cloud_sample_files:
186            profile_conf = cloud_sample_dir + conf_file
187            ret = salt.config._read_conf_file(profile_conf)
188            self.assertEqual(
189                ret,
190                {},
191                "Sample config file '{}' must be commented out.".format(conf_file),
192            )
193
194    def test_conf_cloud_providers_d_files_are_commented(self):
195        """
196        All cloud profile sample configs in salt/conf/cloud.providers.d/* must be completely
197        commented out. This test loops through all of the files in that directory to check
198        for any lines that are not commented or blank.
199        """
200        cloud_sample_dir = SAMPLE_CONF_DIR + "cloud.providers.d/"
201        if not os.path.exists(cloud_sample_dir):
202            self.skipTest(
203                "Sample config directory '{}' is missing.".format(cloud_sample_dir)
204            )
205        cloud_sample_files = os.listdir(cloud_sample_dir)
206        for conf_file in cloud_sample_files:
207            provider_conf = cloud_sample_dir + conf_file
208            ret = salt.config._read_conf_file(provider_conf)
209            self.assertEqual(
210                ret,
211                {},
212                "Sample config file '{}' must be commented out.".format(conf_file),
213            )
214
215    def test_conf_cloud_maps_d_files_are_commented(self):
216        """
217        All cloud profile sample configs in salt/conf/cloud.maps.d/* must be completely
218        commented out. This test loops through all of the files in that directory to check
219        for any lines that are not commented or blank.
220        """
221        cloud_sample_dir = SAMPLE_CONF_DIR + "cloud.maps.d/"
222        if not os.path.exists(cloud_sample_dir):
223            self.skipTest(
224                "Sample config directory '{}' is missing.".format(cloud_sample_dir)
225            )
226        cloud_sample_files = os.listdir(cloud_sample_dir)
227        for conf_file in cloud_sample_files:
228            map_conf = cloud_sample_dir + conf_file
229            ret = salt.config._read_conf_file(map_conf)
230            self.assertEqual(
231                ret,
232                {},
233                "Sample config file '{}' must be commented out.".format(conf_file),
234            )
235
236
237def _unhandled_mock_read(filename):
238    """
239    Raise an error because we should not be calling salt.utils.files.fopen()
240    """
241    raise CommandExecutionError("Unhandled mock read for {}".format(filename))
242
243
244def _salt_configuration_error(filename):
245    """
246    Raise an error to indicate error in the Salt configuration file
247    """
248    raise SaltConfigurationError("Configuration error in {}".format(filename))
249
250
251class ConfigTestCase(TestCase, AdaptedConfigurationTestCaseMixin):
252    @with_tempfile()
253    def test_sha256_is_default_for_master(self, fpath):
254        with salt.utils.files.fopen(fpath, "w") as wfh:
255            wfh.write("root_dir: /\nkey_logfile: key\n")
256        config = salt.config.master_config(fpath)
257        self.assertEqual(config["hash_type"], "sha256")
258
259    @with_tempfile()
260    def test_sha256_is_default_for_minion(self, fpath):
261        with salt.utils.files.fopen(fpath, "w") as wfh:
262            wfh.write("root_dir: /\nkey_logfile: key\n")
263        config = salt.config.minion_config(fpath)
264        self.assertEqual(config["hash_type"], "sha256")
265
266    @with_tempfile()
267    def test_proper_path_joining(self, fpath):
268        temp_config = "root_dir: /\nkey_logfile: key\n"
269        if salt.utils.platform.is_windows():
270            temp_config = "root_dir: c:\\\nkey_logfile: key\n"
271        with salt.utils.files.fopen(fpath, "w") as fp_:
272            fp_.write(temp_config)
273
274        config = salt.config.master_config(fpath)
275        expect_path_join = os.path.join("/", "key")
276        expect_sep_join = "//key"
277        if salt.utils.platform.is_windows():
278            expect_path_join = os.path.join("c:\\", "key")
279            expect_sep_join = "c:\\\\key"
280
281        # os.path.join behavior
282        self.assertEqual(config["key_logfile"], expect_path_join)
283        # os.sep.join behavior
284        self.assertNotEqual(config["key_logfile"], expect_sep_join)
285
286    @with_tempdir()
287    def test_common_prefix_stripping(self, tempdir):
288        root_dir = os.path.join(tempdir, "foo", "bar")
289        os.makedirs(root_dir)
290        fpath = os.path.join(root_dir, "config")
291        with salt.utils.files.fopen(fpath, "w") as fp_:
292            fp_.write("root_dir: {}\nlog_file: {}\n".format(root_dir, fpath))
293        config = salt.config.master_config(fpath)
294        self.assertEqual(config["log_file"], fpath)
295
296    @with_tempdir()
297    def test_default_root_dir_included_in_config_root_dir(self, tempdir):
298        root_dir = os.path.join(tempdir, "foo", "bar")
299        os.makedirs(root_dir)
300        fpath = os.path.join(root_dir, "config")
301        with salt.utils.files.fopen(fpath, "w") as fp_:
302            fp_.write("root_dir: {}\nlog_file: {}\n".format(root_dir, fpath))
303        config = salt.config.master_config(fpath)
304        self.assertEqual(config["log_file"], fpath)
305
306    @skipIf(
307        salt.utils.platform.is_windows(),
308        "You can't set an environment dynamically in Windows",
309    )
310    @with_tempdir()
311    def test_load_master_config_from_environ_var(self, tempdir):
312        env_root_dir = os.path.join(tempdir, "foo", "env")
313        os.makedirs(env_root_dir)
314        env_fpath = os.path.join(env_root_dir, "config-env")
315
316        with salt.utils.files.fopen(env_fpath, "w") as fp_:
317            fp_.write("root_dir: {}\nlog_file: {}\n".format(env_root_dir, env_fpath))
318        with patched_environ(SALT_MASTER_CONFIG=env_fpath):
319            # Should load from env variable, not the default configuration file.
320            config = salt.config.master_config("{}/master".format(CONFIG_DIR))
321            self.assertEqual(config["log_file"], env_fpath)
322
323        root_dir = os.path.join(tempdir, "foo", "bar")
324        os.makedirs(root_dir)
325        fpath = os.path.join(root_dir, "config")
326        with salt.utils.files.fopen(fpath, "w") as fp_:
327            fp_.write("root_dir: {}\nlog_file: {}\n".format(root_dir, fpath))
328        # Let's set the environment variable, yet, since the configuration
329        # file path is not the default one, i.e., the user has passed an
330        # alternative configuration file form the CLI parser, the
331        # environment variable will be ignored.
332        with patched_environ(SALT_MASTER_CONFIG=env_fpath):
333            config = salt.config.master_config(fpath)
334            self.assertEqual(config["log_file"], fpath)
335
336    @skipIf(
337        salt.utils.platform.is_windows(),
338        "You can't set an environment dynamically in Windows",
339    )
340    @with_tempdir()
341    def test_load_minion_config_from_environ_var(self, tempdir):
342        env_root_dir = os.path.join(tempdir, "foo", "env")
343        os.makedirs(env_root_dir)
344        env_fpath = os.path.join(env_root_dir, "config-env")
345
346        with salt.utils.files.fopen(env_fpath, "w") as fp_:
347            fp_.write("root_dir: {}\nlog_file: {}\n".format(env_root_dir, env_fpath))
348
349        with patched_environ(SALT_MINION_CONFIG=env_fpath):
350            # Should load from env variable, not the default configuration file
351            config = salt.config.minion_config("{}/minion".format(CONFIG_DIR))
352            self.assertEqual(config["log_file"], env_fpath)
353
354        root_dir = os.path.join(tempdir, "foo", "bar")
355        os.makedirs(root_dir)
356        fpath = os.path.join(root_dir, "config")
357        with salt.utils.files.fopen(fpath, "w") as fp_:
358            fp_.write("root_dir: {}\nlog_file: {}\n".format(root_dir, fpath))
359        # Let's set the environment variable, yet, since the configuration
360        # file path is not the default one, i.e., the user has passed an
361        # alternative configuration file form the CLI parser, the
362        # environment variable will be ignored.
363        with patched_environ(SALT_MINION_CONFIG=env_fpath):
364            config = salt.config.minion_config(fpath)
365            self.assertEqual(config["log_file"], fpath)
366
367    @skipIf(
368        salt.utils.platform.is_windows(),
369        "You can't set an environment dynamically in Windows",
370    )
371    @with_tempdir()
372    def test_load_client_config_from_environ_var(self, tempdir):
373        env_root_dir = os.path.join(tempdir, "foo", "env")
374        os.makedirs(env_root_dir)
375
376        # Let's populate a master configuration file which should not get
377        # picked up since the client configuration tries to load the master
378        # configuration settings using the provided client configuration
379        # file
380        master_config = os.path.join(env_root_dir, "master")
381        with salt.utils.files.fopen(master_config, "w") as fp_:
382            fp_.write(
383                "blah: true\nroot_dir: {}\nlog_file: {}\n".format(
384                    env_root_dir, master_config
385                )
386            )
387
388        # Now the client configuration file
389        env_fpath = os.path.join(env_root_dir, "config-env")
390        with salt.utils.files.fopen(env_fpath, "w") as fp_:
391            fp_.write("root_dir: {}\nlog_file: {}\n".format(env_root_dir, env_fpath))
392
393        with patched_environ(
394            SALT_MASTER_CONFIG=master_config, SALT_CLIENT_CONFIG=env_fpath
395        ):
396            # Should load from env variable, not the default configuration file
397            config = salt.config.client_config(os.path.expanduser("~/.salt"))
398            self.assertEqual(config["log_file"], env_fpath)
399            self.assertTrue("blah" not in config)
400
401        root_dir = os.path.join(tempdir, "foo", "bar")
402        os.makedirs(root_dir)
403        fpath = os.path.join(root_dir, "config")
404        with salt.utils.files.fopen(fpath, "w") as fp_:
405            fp_.write("root_dir: {}\nlog_file: {}\n".format(root_dir, fpath))
406        # Let's set the environment variable, yet, since the configuration
407        # file path is not the default one, i.e., the user has passed an
408        # alternative configuration file form the CLI parser, the
409        # environment variable will be ignored.
410        with patched_environ(
411            SALT_MASTER_CONFIG=env_fpath, SALT_CLIENT_CONFIG=env_fpath
412        ):
413            config = salt.config.master_config(fpath)
414            self.assertEqual(config["log_file"], fpath)
415
416    @with_tempdir()
417    def test_issue_5970_minion_confd_inclusion(self, tempdir):
418        minion_config = os.path.join(tempdir, "minion")
419        minion_confd = os.path.join(tempdir, "minion.d")
420        os.makedirs(minion_confd)
421
422        # Let's populate a minion configuration file with some basic
423        # settings
424        with salt.utils.files.fopen(minion_config, "w") as fp_:
425            fp_.write(
426                "blah: false\nroot_dir: {}\nlog_file: {}\n".format(
427                    tempdir, minion_config
428                )
429            )
430
431        # Now, let's populate an extra configuration file under minion.d
432        # Notice that above we've set blah as False and below as True.
433        # Since the minion.d files are loaded after the main configuration
434        # file so overrides can happen, the final value of blah should be
435        # True.
436        extra_config = os.path.join(minion_confd, "extra.conf")
437        with salt.utils.files.fopen(extra_config, "w") as fp_:
438            fp_.write("blah: true\n")
439
440        # Let's load the configuration
441        config = salt.config.minion_config(minion_config)
442
443        self.assertEqual(config["log_file"], minion_config)
444        # As proven by the assertion below, blah is True
445        self.assertTrue(config["blah"])
446
447    @with_tempdir()
448    def test_master_confd_inclusion(self, tempdir):
449        master_config = os.path.join(tempdir, "master")
450        master_confd = os.path.join(tempdir, "master.d")
451        os.makedirs(master_confd)
452
453        # Let's populate a master configuration file with some basic
454        # settings
455        with salt.utils.files.fopen(master_config, "w") as fp_:
456            fp_.write(
457                "blah: false\nroot_dir: {}\nlog_file: {}\n".format(
458                    tempdir, master_config
459                )
460            )
461
462        # Now, let's populate an extra configuration file under master.d
463        # Notice that above we've set blah as False and below as True.
464        # Since the master.d files are loaded after the main configuration
465        # file so overrides can happen, the final value of blah should be
466        # True.
467        extra_config = os.path.join(master_confd, "extra.conf")
468        with salt.utils.files.fopen(extra_config, "w") as fp_:
469            fp_.write("blah: true\n")
470
471        # Let's load the configuration
472        config = salt.config.master_config(master_config)
473
474        self.assertEqual(config["log_file"], master_config)
475        # As proven by the assertion below, blah is True
476        self.assertTrue(config["blah"])
477
478    @with_tempfile()
479    @with_tempdir()
480    def test_master_file_roots_glob(self, tempdir, fpath):
481        # Create some files
482        for f in "abc":
483            fpath = os.path.join(tempdir, f)
484            with salt.utils.files.fopen(fpath, "w") as wfh:
485                wfh.write(f)
486
487        with salt.utils.files.fopen(fpath, "w") as wfh:
488            wfh.write(
489                "file_roots:\n  base:\n    - {}".format(os.path.join(tempdir, "*"))
490            )
491        config = salt.config.master_config(fpath)
492        base = config["file_roots"]["base"]
493        self.assertEqual(
494            set(base),
495            {
496                os.path.join(tempdir, "a"),
497                os.path.join(tempdir, "b"),
498                os.path.join(tempdir, "c"),
499            },
500        )
501
502    def test_validate_bad_file_roots(self):
503        expected = salt.config._expand_glob_path([salt.syspaths.BASE_FILE_ROOTS_DIR])
504        with patch("salt.config._normalize_roots") as mk:
505            ret = salt.config._validate_file_roots(None)
506            assert not mk.called
507        assert ret == {"base": expected}
508
509    @with_tempfile()
510    @with_tempdir()
511    def test_master_pillar_roots_glob(self, tempdir, fpath):
512        # Create some files.
513        for f in "abc":
514            fpath = os.path.join(tempdir, f)
515            with salt.utils.files.fopen(fpath, "w") as wfh:
516                wfh.write(f)
517
518        with salt.utils.files.fopen(fpath, "w") as wfh:
519            wfh.write(
520                "pillar_roots:\n  base:\n    - {}".format(os.path.join(tempdir, "*"))
521            )
522        config = salt.config.master_config(fpath)
523        base = config["pillar_roots"]["base"]
524        self.assertEqual(
525            set(base),
526            {
527                os.path.join(tempdir, "a"),
528                os.path.join(tempdir, "b"),
529                os.path.join(tempdir, "c"),
530            },
531        )
532
533    def test_validate_bad_pillar_roots(self):
534        expected = salt.config._expand_glob_path([salt.syspaths.BASE_PILLAR_ROOTS_DIR])
535        with patch("salt.config._normalize_roots") as mk:
536            ret = salt.config._validate_pillar_roots(None)
537            assert not mk.called
538        assert ret == {"base": expected}
539
540    @with_tempdir()
541    @pytest.mark.slow_test
542    def test_master_id_function(self, tempdir):
543        master_config = os.path.join(tempdir, "master")
544
545        with salt.utils.files.fopen(master_config, "w") as fp_:
546            fp_.write(
547                "id_function:\n"
548                "  test.echo:\n"
549                "    text: hello_world\n"
550                "root_dir: {}\n"
551                "log_file: {}\n".format(tempdir, master_config)
552            )
553
554        # Let's load the configuration
555        config = salt.config.master_config(master_config)
556
557        self.assertEqual(config["log_file"], master_config)
558        # 'master_config' appends '_master' to the ID
559        self.assertEqual(config["id"], "hello_world_master")
560
561    @with_tempfile()
562    @with_tempdir()
563    def test_minion_file_roots_glob(self, tempdir, fpath):
564        # Create some files.
565        for f in "abc":
566            fpath = os.path.join(tempdir, f)
567            with salt.utils.files.fopen(fpath, "w") as wfh:
568                wfh.write(f)
569
570        with salt.utils.files.fopen(fpath, "w") as wfh:
571            wfh.write(
572                "file_roots:\n  base:\n    - {}".format(os.path.join(tempdir, "*"))
573            )
574        config = salt.config.minion_config(fpath)
575        base = config["file_roots"]["base"]
576        self.assertEqual(
577            set(base),
578            {
579                os.path.join(tempdir, "a"),
580                os.path.join(tempdir, "b"),
581                os.path.join(tempdir, "c"),
582            },
583        )
584
585    @with_tempfile()
586    @with_tempdir()
587    def test_minion_pillar_roots_glob(self, tempdir, fpath):
588        # Create some files.
589        for f in "abc":
590            fpath = os.path.join(tempdir, f)
591            with salt.utils.files.fopen(fpath, "w") as wfh:
592                wfh.write(f)
593
594        with salt.utils.files.fopen(fpath, "w") as wfh:
595            wfh.write(
596                "pillar_roots:\n  base:\n    - {}".format(os.path.join(tempdir, "*"))
597            )
598        config = salt.config.minion_config(fpath)
599        base = config["pillar_roots"]["base"]
600        self.assertEqual(
601            set(base),
602            {
603                os.path.join(tempdir, "a"),
604                os.path.join(tempdir, "b"),
605                os.path.join(tempdir, "c"),
606            },
607        )
608
609    @with_tempdir()
610    @pytest.mark.slow_test
611    def test_minion_id_function(self, tempdir):
612        minion_config = os.path.join(tempdir, "minion")
613
614        with salt.utils.files.fopen(minion_config, "w") as fp_:
615            fp_.write(
616                "id_function:\n"
617                "  test.echo:\n"
618                "    text: hello_world\n"
619                "root_dir: {}\n"
620                "log_file: {}\n".format(tempdir, minion_config)
621            )
622
623        # Let's load the configuration
624        config = salt.config.minion_config(minion_config)
625
626        self.assertEqual(config["log_file"], minion_config)
627        self.assertEqual(config["id"], "hello_world")
628
629    @with_tempdir()
630    @pytest.mark.slow_test
631    def test_minion_id_lowercase(self, tempdir):
632        """
633        This tests that setting `minion_id_lowercase: True` does lower case
634        the minion id. Lowercase does not operate on a static `id: KING_BOB`
635        setting, or a cached id.
636        """
637        minion_config = os.path.join(tempdir, "minion")
638        with salt.utils.files.fopen(minion_config, "w") as fp_:
639            fp_.write(
640                textwrap.dedent(
641                    """\
642                id_function:
643                  test.echo:
644                    text: KING_BOB
645                minion_id_caching: False
646                minion_id_lowercase: True
647            """
648                )
649            )
650        config = salt.config.minion_config(minion_config)  # Load the configuration
651        self.assertEqual(config["minion_id_caching"], False)  # Check the configuration
652        self.assertEqual(config["minion_id_lowercase"], True)  # Check the configuration
653        self.assertEqual(config["id"], "king_bob")
654
655    @with_tempdir()
656    @pytest.mark.slow_test
657    def test_minion_id_remove_domain_string_positive(self, tempdir):
658        """
659        This tests that the values of `minion_id_remove_domain` is suppressed from a generated minion id,
660        effectivly generating a hostname minion_id.
661        """
662        minion_config = os.path.join(tempdir, "minion")
663        with salt.utils.files.fopen(minion_config, "w") as fp_:
664            fp_.write(
665                textwrap.dedent(
666                    """\
667                id_function:
668                  test.echo:
669                    text: king_bob.foo.org
670                minion_id_remove_domain: foo.org
671                minion_id_caching: False
672            """
673                )
674            )
675
676        # Let's load the configuration
677        config = salt.config.minion_config(minion_config)
678        self.assertEqual(config["minion_id_remove_domain"], "foo.org")
679        self.assertEqual(config["id"], "king_bob")
680
681    @with_tempdir()
682    @pytest.mark.slow_test
683    def test_minion_id_remove_domain_string_negative(self, tempdir):
684        """
685        See above
686        """
687        minion_config = os.path.join(tempdir, "minion")
688        with salt.utils.files.fopen(minion_config, "w") as fp_:
689            fp_.write(
690                textwrap.dedent(
691                    """\
692                id_function:
693                  test.echo:
694                    text: king_bob.foo.org
695                minion_id_remove_domain: bar.org
696                minion_id_caching: False
697            """
698                )
699            )
700
701        config = salt.config.minion_config(minion_config)
702        self.assertEqual(config["id"], "king_bob.foo.org")
703
704    @with_tempdir()
705    @pytest.mark.slow_test
706    def test_minion_id_remove_domain_bool_true(self, tempdir):
707        """
708        See above
709        """
710        minion_config = os.path.join(tempdir, "minion")
711        with salt.utils.files.fopen(minion_config, "w") as fp_:
712            fp_.write(
713                textwrap.dedent(
714                    """\
715                id_function:
716                  test.echo:
717                    text: king_bob.foo.org
718                minion_id_remove_domain: True
719                minion_id_caching: False
720            """
721                )
722            )
723        config = salt.config.minion_config(minion_config)
724        self.assertEqual(config["id"], "king_bob")
725
726    @with_tempdir()
727    @pytest.mark.slow_test
728    def test_minion_id_remove_domain_bool_false(self, tempdir):
729        """
730        See above
731        """
732        minion_config = os.path.join(tempdir, "minion")
733        with salt.utils.files.fopen(minion_config, "w") as fp_:
734            fp_.write(
735                textwrap.dedent(
736                    """\
737                id_function:
738                  test.echo:
739                    text: king_bob.foo.org
740                minion_id_remove_domain: False
741                minion_id_caching: False
742            """
743                )
744            )
745        config = salt.config.minion_config(minion_config)
746        self.assertEqual(config["id"], "king_bob.foo.org")
747
748    @with_tempdir()
749    def test_backend_rename(self, tempdir):
750        """
751        This tests that we successfully rename git, hg, svn, and minion to
752        gitfs, hgfs, svnfs, and minionfs in the master and minion opts.
753        """
754        fpath = salt.utils.files.mkstemp(dir=tempdir)
755        with salt.utils.files.fopen(fpath, "w") as fp_:
756            fp_.write(
757                textwrap.dedent(
758                    """\
759                fileserver_backend:
760                  - roots
761                  - git
762                  - hg
763                  - svn
764                  - minion
765                """
766                )
767            )
768
769        master_config = salt.config.master_config(fpath)
770        minion_config = salt.config.minion_config(fpath)
771        expected = ["roots", "gitfs", "hgfs", "svnfs", "minionfs"]
772
773        self.assertEqual(master_config["fileserver_backend"], expected)
774        self.assertEqual(minion_config["fileserver_backend"], expected)
775
776    def test_syndic_config(self):
777        minion_conf_path = self.get_config_file_path("syndic")
778        master_conf_path = os.path.join(os.path.dirname(minion_conf_path), "master")
779        syndic_opts = salt.config.syndic_config(master_conf_path, minion_conf_path)
780        root_dir = syndic_opts["root_dir"]
781        # id & pki dir are shared & so configured on the minion side
782        self.assertEqual(syndic_opts["id"], "syndic")
783        self.assertEqual(syndic_opts["pki_dir"], os.path.join(root_dir, "pki"))
784        # the rest is configured master side
785        self.assertEqual(syndic_opts["master"], "127.0.0.1")
786        self.assertEqual(
787            syndic_opts["sock_dir"], os.path.join(root_dir, "run", "minion")
788        )
789        self.assertEqual(syndic_opts["cachedir"], os.path.join(root_dir, "cache"))
790        self.assertEqual(
791            syndic_opts["log_file"], os.path.join(root_dir, "logs", "syndic.log")
792        )
793        self.assertEqual(
794            syndic_opts["pidfile"], os.path.join(root_dir, "run", "syndic.pid")
795        )
796        # Show that the options of localclient that repub to local master
797        # are not merged with syndic ones
798        self.assertEqual(syndic_opts["_master_conf_file"], minion_conf_path)
799        self.assertEqual(syndic_opts["_minion_conf_file"], master_conf_path)
800
801    @with_tempfile()
802    def _get_tally(self, fpath, conf_func):
803        """
804        This ensures that any strings which are loaded are unicode strings
805        """
806        tally = {}
807
808        def _count_strings(config):
809            if isinstance(config, dict):
810                for key, val in config.items():
811                    log.debug("counting strings in dict key: %s", key)
812                    log.debug("counting strings in dict val: %s", val)
813                    _count_strings(key)
814                    _count_strings(val)
815            elif isinstance(config, list):
816                log.debug("counting strings in list: %s", config)
817                for item in config:
818                    _count_strings(item)
819            else:
820                if isinstance(config, str):
821                    tally["unicode"] = tally.get("unicode", 0) + 1
822
823        with salt.utils.files.fopen(fpath, "w") as wfh:
824            wfh.write(
825                textwrap.dedent(
826                    """
827                foo: bar
828                mylist:
829                  - somestring
830                  - 9
831                  - 123.456
832                  - True
833                  - nested:
834                    - key: val
835                    - nestedlist:
836                      - foo
837                      - bar
838                      - baz
839                mydict:
840                  - somestring: 9
841                  - 123.456: 789
842                  - True: False
843                  - nested:
844                    - key: val
845                    - nestedlist:
846                      - foo
847                      - bar
848                      - baz"""
849                )
850            )
851            if conf_func is salt.config.master_config:
852                wfh.write("\n\n")
853                wfh.write(
854                    textwrap.dedent(
855                        """
856                    rest_cherrypy:
857                      port: 8000
858                      disable_ssl: True
859                      app_path: /beacon_demo
860                      app: /srv/web/html/index.html
861                      static: /srv/web/static"""
862                    )
863                )
864        config = conf_func(fpath)
865        _count_strings(config)
866        return tally
867
868    def test_conf_file_strings_are_unicode_for_master(self):
869        """
870        This ensures that any strings which are loaded are unicode strings
871        """
872        # pylint: disable=no-value-for-parameter
873        tally = self._get_tally(salt.config.master_config)
874        # pylint: enable=no-value-for-parameter
875        non_unicode = tally.get("non_unicode", [])
876        self.assertEqual(len(non_unicode), 0, non_unicode)
877        self.assertTrue(tally["unicode"] > 0)
878
879    def test_conf_file_strings_are_unicode_for_minion(self):
880        """
881        This ensures that any strings which are loaded are unicode strings
882        """
883        # pylint: disable=no-value-for-parameter
884        tally = self._get_tally(salt.config.minion_config)
885        # pylint: enable=no-value-for-parameter
886        non_unicode = tally.get("non_unicode", [])
887        self.assertEqual(len(non_unicode), 0, non_unicode)
888        self.assertTrue(tally["unicode"] > 0)
889
890    def test__read_conf_file_invalid_yaml__schedule_conf(self):
891        """
892        If ``_schedule.conf`` is an invalid file a YAMLError will be thrown
893        which should cause the invalid file to be replaced by ``_schedule.confYAMLError``
894        """
895        import salt.config as config
896
897        yaml_error = MagicMock(side_effect=[salt.utils.yaml.YAMLError])
898        with patch("salt.utils.files.fopen", MagicMock()), patch(
899            "salt.utils.yaml.safe_load", yaml_error
900        ), patch("os.replace") as mock_os:
901            path = os.sep + os.path.join("some", "path", "_schedule.conf")
902            config._read_conf_file(path)
903            mock_os.assert_called_once_with(path, path + "YAMLError")
904
905    def test__read_conf_file_invalid_yaml(self):
906        """
907        Any other file that throws a YAMLError should raise a
908        SaltConfigurationError and should not trigger an os.replace
909        """
910        import salt.config as config
911
912        yaml_error = MagicMock(side_effect=[salt.utils.yaml.YAMLError])
913        with patch("salt.utils.files.fopen", MagicMock()), patch(
914            "salt.utils.yaml.safe_load", yaml_error
915        ), patch("os.replace") as mock_os:
916            path = os.sep + os.path.join("etc", "salt", "minion")
917            self.assertRaises(SaltConfigurationError, config._read_conf_file, path=path)
918            mock_os.assert_not_called()
919
920    def test__read_conf_file_empty_dict(self):
921        """
922        A config file that is not rendered as a dictionary by the YAML loader
923        should also raise a SaltConfigurationError and should not trigger
924        an os.replace
925        """
926        import salt.config as config
927
928        mock_safe_load = MagicMock(return_value="some non dict data")
929        with patch("salt.utils.files.fopen", MagicMock()), patch(
930            "salt.utils.yaml.safe_load", mock_safe_load
931        ), patch("os.replace") as mock_os:
932            path = os.sep + os.path.join("etc", "salt", "minion")
933            self.assertRaises(SaltConfigurationError, config._read_conf_file, path=path)
934            mock_os.assert_not_called()
935
936    def test__read_conf_file_integer_id(self):
937        """
938        An integer id should be a string
939        """
940        import salt.config as config
941
942        mock_safe_load = MagicMock(return_value={"id": 1234})
943        with patch("salt.utils.files.fopen", MagicMock()), patch(
944            "salt.utils.yaml.safe_load", mock_safe_load
945        ), patch("os.replace") as mock_os:
946            path = os.sep + os.path.join("etc", "salt", "minion")
947            expected = {"id": "1234"}
948            result = config._read_conf_file(path)
949            mock_os.assert_not_called()
950            self.assertEqual(expected, result)
951
952    # <---- Salt Cloud Configuration Tests ---------------------------------------------
953
954    # cloud_config tests
955
956    def test_cloud_config_double_master_path(self):
957        """
958        Tests passing in master_config_path and master_config kwargs.
959        """
960        with patch("salt.config.load_config", MagicMock(return_value={})):
961            self.assertRaises(
962                SaltCloudConfigError,
963                salt.config.cloud_config,
964                PATH,
965                master_config_path="foo",
966                master_config="bar",
967            )
968
969    def test_cloud_config_double_providers_path(self):
970        """
971        Tests passing in providers_config_path and providers_config kwargs.
972        """
973        with patch("salt.config.load_config", MagicMock(return_value={})):
974            self.assertRaises(
975                SaltCloudConfigError,
976                salt.config.cloud_config,
977                PATH,
978                providers_config_path="foo",
979                providers_config="bar",
980            )
981
982    def test_cloud_config_double_profiles_path(self):
983        """
984        Tests passing in profiles_config_path and profiles_config kwargs.
985        """
986        with patch("salt.config.load_config", MagicMock(return_value={})):
987            self.assertRaises(
988                SaltCloudConfigError,
989                salt.config.cloud_config,
990                PATH,
991                profiles_config_path="foo",
992                profiles_config="bar",
993            )
994
995    def test_cloud_config_providers_in_opts(self):
996        """
997        Tests mixing old cloud providers with pre-configured providers configurations
998        using the providers_config kwarg
999        """
1000        with patch("salt.config.load_config", MagicMock(return_value={})):
1001            with patch(
1002                "salt.config.apply_cloud_config",
1003                MagicMock(return_value={"providers": "foo"}),
1004            ):
1005                self.assertRaises(
1006                    SaltCloudConfigError,
1007                    salt.config.cloud_config,
1008                    PATH,
1009                    providers_config="bar",
1010                )
1011
1012    def test_cloud_config_providers_in_opts_path(self):
1013        """
1014        Tests mixing old cloud providers with pre-configured providers configurations
1015        using the providers_config_path kwarg
1016        """
1017        with patch("salt.config.load_config", MagicMock(return_value={})):
1018            with patch(
1019                "salt.config.apply_cloud_config",
1020                MagicMock(return_value={"providers": "foo"}),
1021            ):
1022                with patch("os.path.isfile", MagicMock(return_value=True)):
1023                    self.assertRaises(
1024                        SaltCloudConfigError,
1025                        salt.config.cloud_config,
1026                        PATH,
1027                        providers_config_path="bar",
1028                    )
1029
1030    def test_cloud_config_deploy_scripts_search_path(self):
1031        """
1032        Tests the contents of the 'deploy_scripts_search_path' tuple to ensure that
1033        the correct deploy search paths are present.
1034
1035        There should be two search paths reported in the tuple: ``/etc/salt/cloud.deploy.d``
1036        and ``<path-to-salt-install>/salt/cloud/deploy``. The first element is usually
1037        ``/etc/salt/cloud.deploy.d``, but sometimes is can be something like
1038        ``/etc/local/salt/cloud.deploy.d``, so we'll only test against the last part of
1039        the path.
1040        """
1041        with patch("os.path.isdir", MagicMock(return_value=True)):
1042            search_paths = salt.config.cloud_config("/etc/salt/cloud").get(
1043                "deploy_scripts_search_path"
1044            )
1045            etc_deploy_path = "/salt/cloud.deploy.d"
1046            deploy_path = "/salt/cloud/deploy"
1047            if salt.utils.platform.is_windows():
1048                etc_deploy_path = "/salt\\cloud.deploy.d"
1049                deploy_path = "\\salt\\cloud\\deploy"
1050
1051            # Check cloud.deploy.d path is the first element in the search_paths tuple
1052            self.assertTrue(search_paths[0].endswith(etc_deploy_path))
1053
1054            # Check the second element in the search_paths tuple
1055            self.assertTrue(search_paths[1].endswith(deploy_path))
1056
1057    # apply_cloud_config tests
1058
1059    def test_apply_cloud_config_no_provider_detail_list(self):
1060        """
1061        Tests when the provider is not contained in a list of details
1062        """
1063        overrides = {"providers": {"foo": [{"bar": "baz"}]}}
1064        self.assertRaises(
1065            SaltCloudConfigError,
1066            salt.config.apply_cloud_config,
1067            overrides,
1068            defaults=DEFAULT,
1069        )
1070
1071    def test_apply_cloud_config_no_provider_detail_dict(self):
1072        """
1073        Tests when the provider is not contained in the details dictionary
1074        """
1075        overrides = {"providers": {"foo": {"bar": "baz"}}}
1076        self.assertRaises(
1077            SaltCloudConfigError,
1078            salt.config.apply_cloud_config,
1079            overrides,
1080            defaults=DEFAULT,
1081        )
1082
1083    def test_apply_cloud_config_success_list(self):
1084        """
1085        Tests success when valid data is passed into the function as a list
1086        """
1087        with patch(
1088            "salt.config.old_to_new",
1089            MagicMock(
1090                return_value={
1091                    "default_include": "path/to/some/cloud/conf/file",
1092                    "providers": {"foo": {"bar": {"driver": "foo:bar"}}},
1093                }
1094            ),
1095        ):
1096            overrides = {"providers": {"foo": [{"driver": "bar"}]}}
1097            ret = {
1098                "default_include": "path/to/some/cloud/conf/file",
1099                "providers": {"foo": {"bar": {"driver": "foo:bar"}}},
1100            }
1101            self.assertEqual(
1102                salt.config.apply_cloud_config(overrides, defaults=DEFAULT), ret
1103            )
1104
1105    def test_apply_cloud_config_success_dict(self):
1106        """
1107        Tests success when valid data is passed into function as a dictionary
1108        """
1109        with patch(
1110            "salt.config.old_to_new",
1111            MagicMock(
1112                return_value={
1113                    "default_include": "path/to/some/cloud/conf/file",
1114                    "providers": {"foo": {"bar": {"driver": "foo:bar"}}},
1115                }
1116            ),
1117        ):
1118            overrides = {"providers": {"foo": {"driver": "bar"}}}
1119            ret = {
1120                "default_include": "path/to/some/cloud/conf/file",
1121                "providers": {"foo": {"bar": {"driver": "foo:bar"}}},
1122            }
1123            self.assertEqual(
1124                salt.config.apply_cloud_config(overrides, defaults=DEFAULT), ret
1125            )
1126
1127    # apply_vm_profiles_config tests
1128
1129    def test_apply_vm_profiles_config_bad_profile_format(self):
1130        """
1131        Tests passing in a bad profile format in overrides
1132        """
1133        overrides = {"foo": "bar", "conf_file": PATH}
1134        self.assertRaises(
1135            SaltCloudConfigError,
1136            salt.config.apply_vm_profiles_config,
1137            PATH,
1138            overrides,
1139            defaults=DEFAULT,
1140        )
1141
1142    def test_apply_vm_profiles_config_success(self):
1143        """
1144        Tests passing in valid provider and profile config files successfully
1145        """
1146        providers = {
1147            "test-provider": {
1148                "digitalocean": {"driver": "digitalocean", "profiles": {}}
1149            }
1150        }
1151        overrides = {
1152            "test-profile": {
1153                "provider": "test-provider",
1154                "image": "Ubuntu 12.10 x64",
1155                "size": "512MB",
1156            },
1157            "conf_file": PATH,
1158        }
1159        ret = {
1160            "test-profile": {
1161                "profile": "test-profile",
1162                "provider": "test-provider:digitalocean",
1163                "image": "Ubuntu 12.10 x64",
1164                "size": "512MB",
1165            }
1166        }
1167        self.assertEqual(
1168            salt.config.apply_vm_profiles_config(
1169                providers, overrides, defaults=DEFAULT
1170            ),
1171            ret,
1172        )
1173
1174    def test_apply_vm_profiles_config_extend_success(self):
1175        """
1176        Tests profile extends functionality with valid provider and profile configs
1177        """
1178        providers = {"test-config": {"ec2": {"profiles": {}, "driver": "ec2"}}}
1179        overrides = {
1180            "Amazon": {"image": "test-image-1", "extends": "dev-instances"},
1181            "Fedora": {"image": "test-image-2", "extends": "dev-instances"},
1182            "conf_file": PATH,
1183            "dev-instances": {"ssh_username": "test_user", "provider": "test-config"},
1184        }
1185        ret = {
1186            "Amazon": {
1187                "profile": "Amazon",
1188                "ssh_username": "test_user",
1189                "image": "test-image-1",
1190                "provider": "test-config:ec2",
1191            },
1192            "Fedora": {
1193                "profile": "Fedora",
1194                "ssh_username": "test_user",
1195                "image": "test-image-2",
1196                "provider": "test-config:ec2",
1197            },
1198            "dev-instances": {
1199                "profile": "dev-instances",
1200                "ssh_username": "test_user",
1201                "provider": "test-config:ec2",
1202            },
1203        }
1204        self.assertEqual(
1205            salt.config.apply_vm_profiles_config(
1206                providers, overrides, defaults=DEFAULT
1207            ),
1208            ret,
1209        )
1210
1211    def test_apply_vm_profiles_config_extend_override_success(self):
1212        """
1213        Tests profile extends and recursively merges data elements
1214        """
1215        self.maxDiff = None
1216        providers = {"test-config": {"ec2": {"profiles": {}, "driver": "ec2"}}}
1217        overrides = {
1218            "Fedora": {
1219                "image": "test-image-2",
1220                "extends": "dev-instances",
1221                "minion": {"grains": {"stage": "experimental"}},
1222            },
1223            "conf_file": PATH,
1224            "dev-instances": {
1225                "ssh_username": "test_user",
1226                "provider": "test-config",
1227                "minion": {"grains": {"role": "webserver"}},
1228            },
1229        }
1230        ret = {
1231            "Fedora": {
1232                "profile": "Fedora",
1233                "ssh_username": "test_user",
1234                "image": "test-image-2",
1235                "minion": {"grains": {"role": "webserver", "stage": "experimental"}},
1236                "provider": "test-config:ec2",
1237            },
1238            "dev-instances": {
1239                "profile": "dev-instances",
1240                "ssh_username": "test_user",
1241                "minion": {"grains": {"role": "webserver"}},
1242                "provider": "test-config:ec2",
1243            },
1244        }
1245        self.assertEqual(
1246            salt.config.apply_vm_profiles_config(
1247                providers, overrides, defaults=DEFAULT
1248            ),
1249            ret,
1250        )
1251
1252    # apply_cloud_providers_config tests
1253
1254    def test_apply_cloud_providers_config_same_providers(self):
1255        """
1256        Tests when two providers are given with the same provider name
1257        """
1258        overrides = {
1259            "my-dev-envs": [
1260                {
1261                    "id": "ABCDEFGHIJKLMNOP",
1262                    "key": "supersecretkeysupersecretkey",
1263                    "driver": "ec2",
1264                },
1265                {
1266                    "apikey": "abcdefghijklmnopqrstuvwxyz",
1267                    "password": "supersecret",
1268                    "driver": "ec2",
1269                },
1270            ],
1271            "conf_file": PATH,
1272        }
1273        self.assertRaises(
1274            SaltCloudConfigError,
1275            salt.config.apply_cloud_providers_config,
1276            overrides,
1277            DEFAULT,
1278        )
1279
1280    def test_apply_cloud_providers_config_extend(self):
1281        """
1282        Tests the successful extension of a cloud provider
1283        """
1284        overrides = {
1285            "my-production-envs": [
1286                {
1287                    "extends": "my-dev-envs:ec2",
1288                    "location": "us-east-1",
1289                    "user": "ec2-user@mycorp.com",
1290                }
1291            ],
1292            "my-dev-envs": [
1293                {
1294                    "id": "ABCDEFGHIJKLMNOP",
1295                    "user": "user@mycorp.com",
1296                    "location": "ap-southeast-1",
1297                    "key": "supersecretkeysupersecretkey",
1298                    "driver": "ec2",
1299                },
1300                {
1301                    "apikey": "abcdefghijklmnopqrstuvwxyz",
1302                    "password": "supersecret",
1303                    "driver": "linode",
1304                },
1305                {
1306                    "id": "a-tencentcloud-id",
1307                    "key": "a-tencentcloud-key",
1308                    "location": "ap-guangzhou",
1309                    "driver": "tencentcloud",
1310                },
1311            ],
1312            "conf_file": PATH,
1313        }
1314        ret = {
1315            "my-production-envs": {
1316                "ec2": {
1317                    "profiles": {},
1318                    "location": "us-east-1",
1319                    "key": "supersecretkeysupersecretkey",
1320                    "driver": "ec2",
1321                    "id": "ABCDEFGHIJKLMNOP",
1322                    "user": "ec2-user@mycorp.com",
1323                }
1324            },
1325            "my-dev-envs": {
1326                "linode": {
1327                    "apikey": "abcdefghijklmnopqrstuvwxyz",
1328                    "password": "supersecret",
1329                    "profiles": {},
1330                    "driver": "linode",
1331                },
1332                "tencentcloud": {
1333                    "id": "a-tencentcloud-id",
1334                    "key": "a-tencentcloud-key",
1335                    "location": "ap-guangzhou",
1336                    "profiles": {},
1337                    "driver": "tencentcloud",
1338                },
1339                "ec2": {
1340                    "profiles": {},
1341                    "location": "ap-southeast-1",
1342                    "key": "supersecretkeysupersecretkey",
1343                    "driver": "ec2",
1344                    "id": "ABCDEFGHIJKLMNOP",
1345                    "user": "user@mycorp.com",
1346                },
1347            },
1348        }
1349        self.assertEqual(
1350            ret, salt.config.apply_cloud_providers_config(overrides, defaults=DEFAULT)
1351        )
1352
1353    def test_apply_cloud_providers_config_extend_multiple(self):
1354        """
1355        Tests the successful extension of two cloud providers
1356        """
1357        overrides = {
1358            "my-production-envs": [
1359                {
1360                    "extends": "my-dev-envs:ec2",
1361                    "location": "us-east-1",
1362                    "user": "ec2-user@mycorp.com",
1363                },
1364                {
1365                    "password": "new-password",
1366                    "extends": "my-dev-envs:linode",
1367                    "location": "Salt Lake City",
1368                },
1369                {
1370                    "extends": "my-dev-envs:tencentcloud",
1371                    "id": "new-id",
1372                    "key": "new-key",
1373                    "location": "ap-beijing",
1374                },
1375            ],
1376            "my-dev-envs": [
1377                {
1378                    "id": "ABCDEFGHIJKLMNOP",
1379                    "user": "user@mycorp.com",
1380                    "location": "ap-southeast-1",
1381                    "key": "supersecretkeysupersecretkey",
1382                    "driver": "ec2",
1383                },
1384                {
1385                    "apikey": "abcdefghijklmnopqrstuvwxyz",
1386                    "password": "supersecret",
1387                    "driver": "linode",
1388                },
1389                {
1390                    "id": "the-tencentcloud-id",
1391                    "location": "ap-beijing",
1392                    "key": "the-tencentcloud-key",
1393                    "driver": "tencentcloud",
1394                },
1395            ],
1396            "conf_file": PATH,
1397        }
1398        ret = {
1399            "my-production-envs": {
1400                "linode": {
1401                    "apikey": "abcdefghijklmnopqrstuvwxyz",
1402                    "profiles": {},
1403                    "location": "Salt Lake City",
1404                    "driver": "linode",
1405                    "password": "new-password",
1406                },
1407                "ec2": {
1408                    "user": "ec2-user@mycorp.com",
1409                    "key": "supersecretkeysupersecretkey",
1410                    "driver": "ec2",
1411                    "id": "ABCDEFGHIJKLMNOP",
1412                    "profiles": {},
1413                    "location": "us-east-1",
1414                },
1415                "tencentcloud": {
1416                    "id": "new-id",
1417                    "key": "new-key",
1418                    "location": "ap-beijing",
1419                    "profiles": {},
1420                    "driver": "tencentcloud",
1421                },
1422            },
1423            "my-dev-envs": {
1424                "linode": {
1425                    "apikey": "abcdefghijklmnopqrstuvwxyz",
1426                    "password": "supersecret",
1427                    "profiles": {},
1428                    "driver": "linode",
1429                },
1430                "ec2": {
1431                    "profiles": {},
1432                    "user": "user@mycorp.com",
1433                    "key": "supersecretkeysupersecretkey",
1434                    "driver": "ec2",
1435                    "id": "ABCDEFGHIJKLMNOP",
1436                    "location": "ap-southeast-1",
1437                },
1438                "tencentcloud": {
1439                    "id": "the-tencentcloud-id",
1440                    "key": "the-tencentcloud-key",
1441                    "location": "ap-beijing",
1442                    "profiles": {},
1443                    "driver": "tencentcloud",
1444                },
1445            },
1446        }
1447        self.assertEqual(
1448            ret, salt.config.apply_cloud_providers_config(overrides, defaults=DEFAULT)
1449        )
1450
1451    def test_apply_cloud_providers_config_extends_bad_alias(self):
1452        """
1453        Tests when the extension contains an alias not found in providers list
1454        """
1455        overrides = {
1456            "my-production-envs": [
1457                {
1458                    "extends": "test-alias:ec2",
1459                    "location": "us-east-1",
1460                    "user": "ec2-user@mycorp.com",
1461                }
1462            ],
1463            "my-dev-envs": [
1464                {
1465                    "id": "ABCDEFGHIJKLMNOP",
1466                    "user": "user@mycorp.com",
1467                    "location": "ap-southeast-1",
1468                    "key": "supersecretkeysupersecretkey",
1469                    "driver": "ec2",
1470                }
1471            ],
1472            "conf_file": PATH,
1473        }
1474        self.assertRaises(
1475            SaltCloudConfigError,
1476            salt.config.apply_cloud_providers_config,
1477            overrides,
1478            DEFAULT,
1479        )
1480
1481    def test_apply_cloud_providers_config_extends_bad_provider(self):
1482        """
1483        Tests when the extension contains a provider not found in providers list
1484        """
1485        overrides = {
1486            "my-production-envs": [
1487                {
1488                    "extends": "my-dev-envs:linode",
1489                    "location": "us-east-1",
1490                    "user": "ec2-user@mycorp.com",
1491                },
1492                {
1493                    "extends": "my-dev-envs:tencentcloud",
1494                    "location": "ap-shanghai",
1495                    "id": "the-tencentcloud-id",
1496                },
1497            ],
1498            "my-dev-envs": [
1499                {
1500                    "id": "ABCDEFGHIJKLMNOP",
1501                    "user": "user@mycorp.com",
1502                    "location": "ap-southeast-1",
1503                    "key": "supersecretkeysupersecretkey",
1504                    "driver": "ec2",
1505                }
1506            ],
1507            "conf_file": PATH,
1508        }
1509        self.assertRaises(
1510            SaltCloudConfigError,
1511            salt.config.apply_cloud_providers_config,
1512            overrides,
1513            DEFAULT,
1514        )
1515
1516    def test_apply_cloud_providers_config_extends_no_provider(self):
1517        """
1518        Tests when no provider is supplied in the extends statement
1519        """
1520        overrides = {
1521            "my-production-envs": [
1522                {
1523                    "extends": "my-dev-envs",
1524                    "location": "us-east-1",
1525                    "user": "ec2-user@mycorp.com",
1526                },
1527                {
1528                    "extends": "my-dev-envs:tencentcloud",
1529                    "location": "ap-shanghai",
1530                    "id": "the-tencentcloud-id",
1531                },
1532            ],
1533            "my-dev-envs": [
1534                {
1535                    "id": "ABCDEFGHIJKLMNOP",
1536                    "user": "user@mycorp.com",
1537                    "location": "ap-southeast-1",
1538                    "key": "supersecretkeysupersecretkey",
1539                    "driver": "linode",
1540                }
1541            ],
1542            "conf_file": PATH,
1543        }
1544        self.assertRaises(
1545            SaltCloudConfigError,
1546            salt.config.apply_cloud_providers_config,
1547            overrides,
1548            DEFAULT,
1549        )
1550
1551    def test_apply_cloud_providers_extends_not_in_providers(self):
1552        """
1553        Tests when extends is not in the list of providers
1554        """
1555        overrides = {
1556            "my-production-envs": [
1557                {
1558                    "extends": "my-dev-envs ec2",
1559                    "location": "us-east-1",
1560                    "user": "ec2-user@mycorp.com",
1561                }
1562            ],
1563            "my-dev-envs": [
1564                {
1565                    "id": "ABCDEFGHIJKLMNOP",
1566                    "user": "user@mycorp.com",
1567                    "location": "ap-southeast-1",
1568                    "key": "supersecretkeysupersecretkey",
1569                    "driver": "linode",
1570                },
1571                {
1572                    "id": "a-tencentcloud-id",
1573                    "key": "a-tencentcloud-key",
1574                    "location": "ap-guangzhou",
1575                    "driver": "tencentcloud",
1576                },
1577            ],
1578            "conf_file": PATH,
1579        }
1580        self.assertRaises(
1581            SaltCloudConfigError,
1582            salt.config.apply_cloud_providers_config,
1583            overrides,
1584            DEFAULT,
1585        )
1586
1587    # is_provider_configured tests
1588
1589    def test_is_provider_configured_no_alias(self):
1590        """
1591        Tests when provider alias is not in opts
1592        """
1593        opts = {"providers": "test"}
1594        provider = "foo:bar"
1595        self.assertFalse(salt.config.is_provider_configured(opts, provider))
1596
1597    def test_is_provider_configured_no_driver(self):
1598        """
1599        Tests when provider driver is not in opts
1600        """
1601        opts = {"providers": {"foo": "baz"}}
1602        provider = "foo:bar"
1603        self.assertFalse(salt.config.is_provider_configured(opts, provider))
1604
1605    def test_is_provider_configured_key_is_none(self):
1606        """
1607        Tests when a required configuration key is not set
1608        """
1609        opts = {"providers": {"foo": {"bar": {"api_key": None}}}}
1610        provider = "foo:bar"
1611        self.assertFalse(
1612            salt.config.is_provider_configured(
1613                opts, provider, required_keys=("api_key",)
1614            )
1615        )
1616
1617    def test_is_provider_configured_success(self):
1618        """
1619        Tests successful cloud provider configuration
1620        """
1621        opts = {"providers": {"foo": {"bar": {"api_key": "baz"}}}}
1622        provider = "foo:bar"
1623        ret = {"api_key": "baz"}
1624        self.assertEqual(
1625            salt.config.is_provider_configured(
1626                opts, provider, required_keys=("api_key",)
1627            ),
1628            ret,
1629        )
1630
1631    def test_is_provider_configured_multiple_driver_not_provider(self):
1632        """
1633        Tests when the drive is not the same as the provider when
1634        searching through multiple providers
1635        """
1636        opts = {"providers": {"foo": {"bar": {"api_key": "baz"}}}}
1637        provider = "foo"
1638        self.assertFalse(salt.config.is_provider_configured(opts, provider))
1639
1640    def test_is_provider_configured_multiple_key_is_none(self):
1641        """
1642        Tests when a required configuration key is not set when
1643        searching through multiple providers
1644        """
1645        opts = {"providers": {"foo": {"bar": {"api_key": None}}}}
1646        provider = "bar"
1647        self.assertFalse(
1648            salt.config.is_provider_configured(
1649                opts, provider, required_keys=("api_key",)
1650            )
1651        )
1652
1653    def test_is_provider_configured_multiple_success(self):
1654        """
1655        Tests successful cloud provider configuration when searching
1656        through multiple providers
1657        """
1658        opts = {"providers": {"foo": {"bar": {"api_key": "baz"}}}}
1659        provider = "bar"
1660        ret = {"api_key": "baz"}
1661        self.assertEqual(
1662            salt.config.is_provider_configured(
1663                opts, provider, required_keys=("api_key",)
1664            ),
1665            ret,
1666        )
1667
1668    # other cloud configuration tests
1669
1670    @skipIf(
1671        salt.utils.platform.is_windows(),
1672        "You can't set an environment dynamically in Windows",
1673    )
1674    @with_tempdir()
1675    def test_load_cloud_config_from_environ_var(self, tempdir):
1676        env_root_dir = os.path.join(tempdir, "foo", "env")
1677        os.makedirs(env_root_dir)
1678        env_fpath = os.path.join(env_root_dir, "config-env")
1679
1680        with salt.utils.files.fopen(env_fpath, "w") as fp_:
1681            fp_.write("root_dir: {}\nlog_file: {}\n".format(env_root_dir, env_fpath))
1682
1683        with patched_environ(SALT_CLOUD_CONFIG=env_fpath):
1684            # Should load from env variable, not the default configuration file
1685            config = salt.config.cloud_config("/etc/salt/cloud")
1686            self.assertEqual(config["log_file"], env_fpath)
1687
1688        root_dir = os.path.join(tempdir, "foo", "bar")
1689        os.makedirs(root_dir)
1690        fpath = os.path.join(root_dir, "config")
1691        with salt.utils.files.fopen(fpath, "w") as fp_:
1692            fp_.write("root_dir: {}\nlog_file: {}\n".format(root_dir, fpath))
1693        # Let's set the environment variable, yet, since the configuration
1694        # file path is not the default one, i.e., the user has passed an
1695        # alternative configuration file form the CLI parser, the
1696        # environment variable will be ignored.
1697        with patched_environ(SALT_CLOUD_CONFIG=env_fpath):
1698            config = salt.config.cloud_config(fpath)
1699            self.assertEqual(config["log_file"], fpath)
1700
1701    @with_tempdir()
1702    def test_deploy_search_path_as_string(self, temp_conf_dir):
1703        config_file_path = os.path.join(temp_conf_dir, "cloud")
1704        deploy_dir_path = os.path.join(temp_conf_dir, "test-deploy.d")
1705        for directory in (temp_conf_dir, deploy_dir_path):
1706            if not os.path.isdir(directory):
1707                os.makedirs(directory)
1708
1709        default_config = salt.config.cloud_config(config_file_path)
1710        default_config["deploy_scripts_search_path"] = deploy_dir_path
1711        with salt.utils.files.fopen(config_file_path, "w") as cfd:
1712            salt.utils.yaml.safe_dump(default_config, cfd, default_flow_style=False)
1713
1714        default_config = salt.config.cloud_config(config_file_path)
1715
1716        # Our custom deploy scripts path was correctly added to the list
1717        self.assertIn(deploy_dir_path, default_config["deploy_scripts_search_path"])
1718
1719        # And it's even the first occurrence as it should
1720        self.assertEqual(
1721            deploy_dir_path, default_config["deploy_scripts_search_path"][0]
1722        )
1723
1724    def test_includes_load(self):
1725        """
1726        Tests that cloud.{providers,profiles}.d directories are loaded, even if not
1727        directly passed in through path
1728        """
1729        config_file = self.get_config_file_path("cloud")
1730        log.debug("Cloud config file path: %s", config_file)
1731        self.assertTrue(
1732            os.path.exists(config_file), "{} does not exist".format(config_file)
1733        )
1734        config = salt.config.cloud_config(config_file)
1735        self.assertIn("providers", config)
1736        self.assertIn("ec2-config", config["providers"])
1737        self.assertIn("ec2-test", config["profiles"])
1738
1739    # <---- Salt Cloud Configuration Tests ---------------------------------------------
1740
1741    def test_include_config_without_errors(self):
1742        """
1743        Tests that include_config function returns valid configuration
1744        """
1745        include_file = "minion.d/my.conf"
1746        config_path = "/etc/salt/minion"
1747        config_opts = {"id": "myminion.example.com"}
1748
1749        with patch("glob.glob", MagicMock(return_value=include_file)):
1750            with patch(
1751                "salt.config._read_conf_file", MagicMock(return_value=config_opts)
1752            ):
1753                configuration = salt.config.include_config(
1754                    include_file, config_path, verbose=False
1755                )
1756
1757        self.assertEqual(config_opts, configuration)
1758
1759    def test_include_config_with_errors(self):
1760        """
1761        Tests that include_config function returns valid configuration even on errors
1762        """
1763        include_file = "minion.d/my.conf"
1764        config_path = "/etc/salt/minion"
1765        config_opts = {}
1766
1767        with patch("glob.glob", MagicMock(return_value=include_file)):
1768            with patch("salt.config._read_conf_file", _salt_configuration_error):
1769                configuration = salt.config.include_config(
1770                    include_file, config_path, verbose=False
1771                )
1772
1773        self.assertEqual(config_opts, configuration)
1774
1775    def test_include_config_with_errors_exit(self):
1776        """
1777        Tests that include_config exits on errors
1778        """
1779        include_file = "minion.d/my.conf"
1780        config_path = "/etc/salt/minion"
1781
1782        with patch("glob.glob", MagicMock(return_value=include_file)):
1783            with patch("salt.config._read_conf_file", _salt_configuration_error):
1784                with self.assertRaises(SystemExit):
1785                    salt.config.include_config(
1786                        include_file,
1787                        config_path,
1788                        verbose=False,
1789                        exit_on_config_errors=True,
1790                    )
1791
1792    @staticmethod
1793    def _get_defaults(**kwargs):
1794        ret = {
1795            "saltenv": kwargs.pop("saltenv", None),
1796            "id": "test",
1797            "cachedir": "/A",
1798            "sock_dir": "/B",
1799            "root_dir": "/C",
1800            "fileserver_backend": "roots",
1801            "open_mode": False,
1802            "auto_accept": False,
1803            "file_roots": {},
1804            "pillar_roots": {},
1805            "file_ignore_glob": [],
1806            "file_ignore_regex": [],
1807            "worker_threads": 5,
1808            "hash_type": "sha256",
1809            "log_file": "foo.log",
1810        }
1811        ret.update(kwargs)
1812        return ret
1813
1814    def test_apply_config(self):
1815        """
1816        Ensure that the environment and saltenv options work properly
1817        """
1818        with patch.object(
1819            salt.config, "_adjust_log_file_override", Mock()
1820        ), patch.object(salt.config, "_update_ssl_config", Mock()), patch.object(
1821            salt.config, "_update_discovery_config", Mock()
1822        ):
1823            # MASTER CONFIG
1824
1825            # Ensure that environment overrides saltenv when saltenv not
1826            # explicitly passed.
1827            defaults = self._get_defaults(environment="foo")
1828            ret = salt.config.apply_master_config(defaults=defaults)
1829            self.assertEqual(ret["environment"], "foo")
1830            self.assertEqual(ret["saltenv"], "foo")
1831
1832            # Ensure that environment overrides saltenv when saltenv not
1833            # explicitly passed.
1834            defaults = self._get_defaults(environment="foo", saltenv="bar")
1835            ret = salt.config.apply_master_config(defaults=defaults)
1836            self.assertEqual(ret["environment"], "bar")
1837            self.assertEqual(ret["saltenv"], "bar")
1838
1839            # If environment was not explicitly set, it should not be in the
1840            # opts at all.
1841            defaults = self._get_defaults()
1842            ret = salt.config.apply_master_config(defaults=defaults)
1843            self.assertNotIn("environment", ret)
1844            self.assertEqual(ret["saltenv"], None)
1845
1846            # Same test as above but with saltenv explicitly set
1847            defaults = self._get_defaults(saltenv="foo")
1848            ret = salt.config.apply_master_config(defaults=defaults)
1849            self.assertNotIn("environment", ret)
1850            self.assertEqual(ret["saltenv"], "foo")
1851
1852            # MINION CONFIG
1853
1854            # Ensure that environment overrides saltenv when saltenv not
1855            # explicitly passed.
1856            defaults = self._get_defaults(environment="foo")
1857            ret = salt.config.apply_minion_config(defaults=defaults)
1858            self.assertEqual(ret["environment"], "foo")
1859            self.assertEqual(ret["saltenv"], "foo")
1860
1861            # Ensure that environment overrides saltenv when saltenv not
1862            # explicitly passed.
1863            defaults = self._get_defaults(environment="foo", saltenv="bar")
1864            ret = salt.config.apply_minion_config(defaults=defaults)
1865            self.assertEqual(ret["environment"], "bar")
1866            self.assertEqual(ret["saltenv"], "bar")
1867
1868            # If environment was not explicitly set, it should not be in the
1869            # opts at all.
1870            defaults = self._get_defaults()
1871            ret = salt.config.apply_minion_config(defaults=defaults)
1872            self.assertNotIn("environment", ret)
1873            self.assertEqual(ret["saltenv"], None)
1874
1875            # Same test as above but with saltenv explicitly set
1876            defaults = self._get_defaults(saltenv="foo")
1877            ret = salt.config.apply_minion_config(defaults=defaults)
1878            self.assertNotIn("environment", ret)
1879            self.assertEqual(ret["saltenv"], "foo")
1880
1881    @with_tempfile()
1882    def test_minion_config_role_master(self, fpath):
1883        with salt.utils.files.fopen(fpath, "w") as wfh:
1884            wfh.write("root_dir: /\nkey_logfile: key\n")
1885        with patch("salt.config.apply_sdb") as apply_sdb_mock, patch(
1886            "salt.config._validate_opts"
1887        ) as validate_opts_mock:
1888            config = salt.config.minion_config(fpath, role="master")
1889            apply_sdb_mock.assert_not_called()
1890
1891            validate_opts_mock.assert_not_called()
1892        self.assertEqual(config["__role"], "master")
1893
1894    @with_tempfile()
1895    def test_mminion_config_cache_path(self, fpath):
1896        cachedir = os.path.abspath("/path/to/master/cache")
1897        overrides = {}
1898
1899        with salt.utils.files.fopen(fpath, "w") as wfh:
1900            wfh.write("root_dir: /\nkey_logfile: key\ncachedir: {}".format(cachedir))
1901        config = salt.config.mminion_config(fpath, overrides)
1902        self.assertEqual(config["__role"], "master")
1903        self.assertEqual(config["cachedir"], cachedir)
1904
1905    @with_tempfile()
1906    def test_mminion_config_cache_path_overrides(self, fpath):
1907        cachedir = os.path.abspath("/path/to/master/cache")
1908        overrides = {"cachedir": cachedir}
1909
1910        with salt.utils.files.fopen(fpath, "w") as wfh:
1911            wfh.write("root_dir: /\nkey_logfile: key\n")
1912        config = salt.config.mminion_config(fpath, overrides)
1913        self.assertEqual(config["__role"], "master")
1914        self.assertEqual(config["cachedir"], cachedir)
1915
1916
1917class APIConfigTestCase(DefaultConfigsBase, TestCase):
1918    """
1919    TestCase for the api_config function in salt.config.__init__.py
1920    """
1921
1922    def setUp(self):
1923        # Copy DEFAULT_API_OPTS to restore after the test
1924        self.default_api_opts = salt.config.DEFAULT_API_OPTS.copy()
1925
1926    def tearDown(self):
1927        # Reset DEFAULT_API_OPTS settings as to not interfere with other unit tests
1928        salt.config.DEFAULT_API_OPTS = self.default_api_opts
1929
1930    def test_api_config_log_file_values(self):
1931        """
1932        Tests the opts value of the 'log_file' after running through the
1933        various default dict updates. 'log_file' should be updated to match
1934        the DEFAULT_API_OPTS 'api_logfile' value.
1935        """
1936        with patch(
1937            "salt.config.client_config",
1938            MagicMock(return_value=self.mock_master_default_opts),
1939        ):
1940            expected = "{}/var/log/salt/api".format(
1941                RUNTIME_VARS.TMP_ROOT_DIR if RUNTIME_VARS.TMP_ROOT_DIR != "/" else ""
1942            )
1943            if salt.utils.platform.is_windows():
1944                expected = "{}\\var\\log\\salt\\api".format(RUNTIME_VARS.TMP_ROOT_DIR)
1945
1946            ret = salt.config.api_config("/some/fake/path")
1947            self.assertEqual(ret["log_file"], expected)
1948
1949    def test_api_config_pidfile_values(self):
1950        """
1951        Tests the opts value of the 'pidfile' after running through the
1952        various default dict updates. 'pidfile' should be updated to match
1953        the DEFAULT_API_OPTS 'api_pidfile' value.
1954        """
1955        with patch(
1956            "salt.config.client_config",
1957            MagicMock(return_value=self.mock_master_default_opts),
1958        ):
1959            expected = "{}/var/run/salt-api.pid".format(
1960                RUNTIME_VARS.TMP_ROOT_DIR if RUNTIME_VARS.TMP_ROOT_DIR != "/" else ""
1961            )
1962            if salt.utils.platform.is_windows():
1963                expected = "{}\\var\\run\\salt-api.pid".format(
1964                    RUNTIME_VARS.TMP_ROOT_DIR
1965                )
1966
1967            ret = salt.config.api_config("/some/fake/path")
1968            self.assertEqual(ret["pidfile"], expected)
1969
1970    def test_master_config_file_overrides_defaults(self):
1971        """
1972        Tests the opts value of the api config values after running through the
1973        various default dict updates that should be overridden by settings in
1974        the user's master config file.
1975        """
1976        foo_dir = os.path.join(RUNTIME_VARS.TMP_ROOT_DIR, "foo/bar/baz")
1977        hello_dir = os.path.join(RUNTIME_VARS.TMP_ROOT_DIR, "hello/world")
1978        if salt.utils.platform.is_windows():
1979            foo_dir = "c:\\{}".format(foo_dir.replace("/", "\\"))
1980            hello_dir = "c:\\{}".format(hello_dir.replace("/", "\\"))
1981
1982        mock_master_config = {
1983            "api_pidfile": foo_dir,
1984            "api_logfile": hello_dir,
1985            "rest_timeout": 5,
1986        }
1987        mock_master_config.update(self.mock_master_default_opts.copy())
1988
1989        with patch(
1990            "salt.config.client_config", MagicMock(return_value=mock_master_config)
1991        ):
1992            ret = salt.config.api_config("/some/fake/path")
1993            self.assertEqual(ret["rest_timeout"], 5)
1994            self.assertEqual(ret["api_pidfile"], foo_dir)
1995            self.assertEqual(ret["pidfile"], foo_dir)
1996            self.assertEqual(ret["api_logfile"], hello_dir)
1997            self.assertEqual(ret["log_file"], hello_dir)
1998
1999    def test_api_config_prepend_root_dirs_return(self):
2000        """
2001        Tests the opts value of the api_logfile, log_file, api_pidfile, and pidfile
2002        when a custom root directory is used. This ensures that each of these
2003        values is present in the list of opts keys that should have the root_dir
2004        prepended when the api_config function returns the opts dictionary.
2005        """
2006        mock_log = "/mock/root/var/log/salt/api"
2007        mock_pid = "/mock/root/var/run/salt-api.pid"
2008
2009        mock_master_config = self.mock_master_default_opts.copy()
2010        mock_master_config["root_dir"] = "/mock/root/"
2011
2012        if salt.utils.platform.is_windows():
2013            mock_log = "c:\\mock\\root\\var\\log\\salt\\api"
2014            mock_pid = "c:\\mock\\root\\var\\run\\salt-api.pid"
2015            mock_master_config["root_dir"] = "c:\\mock\\root"
2016
2017        with patch(
2018            "salt.config.client_config", MagicMock(return_value=mock_master_config)
2019        ):
2020            ret = salt.config.api_config("/some/fake/path")
2021            self.assertEqual(ret["api_logfile"], mock_log)
2022            self.assertEqual(ret["log_file"], mock_log)
2023            self.assertEqual(ret["api_pidfile"], mock_pid)
2024            self.assertEqual(ret["pidfile"], mock_pid)
2025