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