1"""
2    :codeauthor: Pedro Algarvio (pedro@algarvio.me)
3    :codeauthor: Alexandru Bleotu (alexandru.bleotu@morganstanley.com)
4
5    tests.unit.pillar_test
6    ~~~~~~~~~~~~~~~~~~~~~~
7"""
8
9
10import logging
11import os
12import shutil
13import tempfile
14import textwrap
15
16import salt.config
17import salt.exceptions
18import salt.fileclient
19import salt.utils.stringutils
20from salt.utils.files import fopen
21from tests.support.helpers import with_tempdir
22from tests.support.mock import MagicMock, patch
23from tests.support.runtests import RUNTIME_VARS
24from tests.support.unit import TestCase
25
26log = logging.getLogger(__name__)
27
28
29class MockFileclient:
30    def __init__(self, cache_file=None, get_state=None, list_states=None):
31        if cache_file is not None:
32            self.cache_file = lambda *x, **y: cache_file
33        if get_state is not None:
34            self.get_state = lambda sls, env: get_state[sls]
35        if list_states is not None:
36            self.list_states = lambda *x, **y: list_states
37
38    # pylint: disable=unused-argument,no-method-argument,method-hidden
39    def cache_file(*args, **kwargs):
40        raise NotImplementedError()
41
42    def get_state(*args, **kwargs):
43        raise NotImplementedError()
44
45    def list_states(*args, **kwargs):
46        raise NotImplementedError()
47
48    # pylint: enable=unused-argument,no-method-argument,method-hidden
49
50
51class PillarTestCase(TestCase):
52    def tearDown(self):
53        for attrname in (
54            "generic_file",
55            "generic_minion_file",
56            "ssh_file",
57            "ssh_minion_file",
58            "top_file",
59        ):
60            try:
61                delattr(self, attrname)
62            except AttributeError:
63                continue
64
65    def test_pillarenv_from_saltenv(self):
66        with patch("salt.pillar.compile_template") as compile_template:
67            opts = {
68                "optimization_order": [0, 1, 2],
69                "renderer": "json",
70                "renderer_blacklist": [],
71                "renderer_whitelist": [],
72                "state_top": "",
73                "pillar_roots": {"dev": [], "base": []},
74                "file_roots": {"dev": [], "base": []},
75                "extension_modules": "",
76                "pillarenv_from_saltenv": True,
77            }
78            grains = {
79                "os": "Ubuntu",
80            }
81            pillar = salt.pillar.Pillar(opts, grains, "mocked-minion", "dev")
82            self.assertEqual(pillar.opts["saltenv"], "dev")
83            self.assertEqual(pillar.opts["pillarenv"], "dev")
84
85    def test_ext_pillar_no_extra_minion_data_val_dict(self):
86        opts = {
87            "optimization_order": [0, 1, 2],
88            "renderer": "json",
89            "renderer_blacklist": [],
90            "renderer_whitelist": [],
91            "state_top": "",
92            "pillar_roots": {"dev": [], "base": []},
93            "file_roots": {"dev": [], "base": []},
94            "extension_modules": "",
95            "pillarenv_from_saltenv": True,
96        }
97        mock_ext_pillar_func = MagicMock()
98        with patch(
99            "salt.loader.pillars",
100            MagicMock(return_value={"fake_ext_pillar": mock_ext_pillar_func}),
101        ):
102            pillar = salt.pillar.Pillar(opts, {}, "mocked-minion", "dev")
103        # ext pillar function doesn't have the extra_minion_data arg
104        with patch(
105            "salt.utils.args.get_function_argspec",
106            MagicMock(return_value=MagicMock(args=[])),
107        ):
108            pillar._external_pillar_data(
109                "fake_pillar", {"arg": "foo"}, "fake_ext_pillar"
110            )
111        mock_ext_pillar_func.assert_called_once_with(
112            "mocked-minion", "fake_pillar", arg="foo"
113        )
114        # ext pillar function has the extra_minion_data arg
115        mock_ext_pillar_func.reset_mock()
116        with patch(
117            "salt.utils.args.get_function_argspec",
118            MagicMock(return_value=MagicMock(args=["extra_minion_data"])),
119        ):
120            pillar._external_pillar_data(
121                "fake_pillar", {"arg": "foo"}, "fake_ext_pillar"
122            )
123        mock_ext_pillar_func.assert_called_once_with(
124            "mocked-minion", "fake_pillar", arg="foo"
125        )
126
127    def test_ext_pillar_no_extra_minion_data_val_list(self):
128        opts = {
129            "optimization_order": [0, 1, 2],
130            "renderer": "json",
131            "renderer_blacklist": [],
132            "renderer_whitelist": [],
133            "state_top": "",
134            "pillar_roots": {"dev": [], "base": []},
135            "file_roots": {"dev": [], "base": []},
136            "extension_modules": "",
137            "pillarenv_from_saltenv": True,
138        }
139        mock_ext_pillar_func = MagicMock()
140        with patch(
141            "salt.loader.pillars",
142            MagicMock(return_value={"fake_ext_pillar": mock_ext_pillar_func}),
143        ):
144            pillar = salt.pillar.Pillar(opts, {}, "mocked-minion", "dev")
145        # ext pillar function doesn't have the extra_minion_data arg
146        with patch(
147            "salt.utils.args.get_function_argspec",
148            MagicMock(return_value=MagicMock(args=[])),
149        ):
150            pillar._external_pillar_data("fake_pillar", ["foo"], "fake_ext_pillar")
151        mock_ext_pillar_func.assert_called_once_with(
152            "mocked-minion", "fake_pillar", "foo"
153        )
154        # ext pillar function has the extra_minion_data arg
155        mock_ext_pillar_func.reset_mock()
156        with patch(
157            "salt.utils.args.get_function_argspec",
158            MagicMock(return_value=MagicMock(args=["extra_minion_data"])),
159        ):
160            pillar._external_pillar_data("fake_pillar", ["foo"], "fake_ext_pillar")
161        mock_ext_pillar_func.assert_called_once_with(
162            "mocked-minion", "fake_pillar", "foo"
163        )
164
165    def test_ext_pillar_no_extra_minion_data_val_elem(self):
166        opts = {
167            "optimization_order": [0, 1, 2],
168            "renderer": "json",
169            "renderer_blacklist": [],
170            "renderer_whitelist": [],
171            "state_top": "",
172            "pillar_roots": {"dev": [], "base": []},
173            "file_roots": {"dev": [], "base": []},
174            "extension_modules": "",
175            "pillarenv_from_saltenv": True,
176        }
177        mock_ext_pillar_func = MagicMock()
178        with patch(
179            "salt.loader.pillars",
180            MagicMock(return_value={"fake_ext_pillar": mock_ext_pillar_func}),
181        ):
182            pillar = salt.pillar.Pillar(opts, {}, "mocked-minion", "dev")
183        # ext pillar function doesn't have the extra_minion_data arg
184        with patch(
185            "salt.utils.args.get_function_argspec",
186            MagicMock(return_value=MagicMock(args=[])),
187        ):
188            pillar._external_pillar_data("fake_pillar", "fake_val", "fake_ext_pillar")
189        mock_ext_pillar_func.assert_called_once_with(
190            "mocked-minion", "fake_pillar", "fake_val"
191        )
192        # ext pillar function has the extra_minion_data arg
193        mock_ext_pillar_func.reset_mock()
194        with patch(
195            "salt.utils.args.get_function_argspec",
196            MagicMock(return_value=MagicMock(args=["extra_minion_data"])),
197        ):
198            pillar._external_pillar_data("fake_pillar", "fake_val", "fake_ext_pillar")
199        mock_ext_pillar_func.assert_called_once_with(
200            "mocked-minion", "fake_pillar", "fake_val"
201        )
202
203    def test_ext_pillar_with_extra_minion_data_val_dict(self):
204        opts = {
205            "optimization_order": [0, 1, 2],
206            "renderer": "json",
207            "renderer_blacklist": [],
208            "renderer_whitelist": [],
209            "state_top": "",
210            "pillar_roots": {"dev": [], "base": []},
211            "file_roots": {"dev": [], "base": []},
212            "extension_modules": "",
213            "pillarenv_from_saltenv": True,
214        }
215        mock_ext_pillar_func = MagicMock()
216        with patch(
217            "salt.loader.pillars",
218            MagicMock(return_value={"fake_ext_pillar": mock_ext_pillar_func}),
219        ):
220            pillar = salt.pillar.Pillar(
221                opts, {}, "mocked-minion", "dev", extra_minion_data={"fake_key": "foo"}
222            )
223        # ext pillar function doesn't have the extra_minion_data arg
224        with patch(
225            "salt.utils.args.get_function_argspec",
226            MagicMock(return_value=MagicMock(args=[])),
227        ):
228            pillar._external_pillar_data(
229                "fake_pillar", {"arg": "foo"}, "fake_ext_pillar"
230            )
231        mock_ext_pillar_func.assert_called_once_with(
232            "mocked-minion", "fake_pillar", arg="foo"
233        )
234        # ext pillar function has the extra_minion_data arg
235        mock_ext_pillar_func.reset_mock()
236        with patch(
237            "salt.utils.args.get_function_argspec",
238            MagicMock(return_value=MagicMock(args=["extra_minion_data"])),
239        ):
240            pillar._external_pillar_data(
241                "fake_pillar", {"arg": "foo"}, "fake_ext_pillar"
242            )
243        mock_ext_pillar_func.assert_called_once_with(
244            "mocked-minion",
245            "fake_pillar",
246            arg="foo",
247            extra_minion_data={"fake_key": "foo"},
248        )
249
250    def test_ext_pillar_with_extra_minion_data_val_list(self):
251        opts = {
252            "optimization_order": [0, 1, 2],
253            "renderer": "json",
254            "renderer_blacklist": [],
255            "renderer_whitelist": [],
256            "state_top": "",
257            "pillar_roots": {"dev": [], "base": []},
258            "file_roots": {"dev": [], "base": []},
259            "extension_modules": "",
260            "pillarenv_from_saltenv": True,
261        }
262        mock_ext_pillar_func = MagicMock()
263        with patch(
264            "salt.loader.pillars",
265            MagicMock(return_value={"fake_ext_pillar": mock_ext_pillar_func}),
266        ):
267            pillar = salt.pillar.Pillar(
268                opts, {}, "mocked-minion", "dev", extra_minion_data={"fake_key": "foo"}
269            )
270        # ext pillar function doesn't have the extra_minion_data arg
271        with patch(
272            "salt.utils.args.get_function_argspec",
273            MagicMock(return_value=MagicMock(args=[])),
274        ):
275            pillar._external_pillar_data("fake_pillar", ["bar"], "fake_ext_pillar")
276        mock_ext_pillar_func.assert_called_once_with(
277            "mocked-minion", "fake_pillar", "bar"
278        )
279        # ext pillar function has the extra_minion_data arg
280        mock_ext_pillar_func.reset_mock()
281        with patch(
282            "salt.utils.args.get_function_argspec",
283            MagicMock(return_value=MagicMock(args=["extra_minion_data"])),
284        ):
285            pillar._external_pillar_data("fake_pillar", ["bar"], "fake_ext_pillar")
286        mock_ext_pillar_func.assert_called_once_with(
287            "mocked-minion", "fake_pillar", "bar", extra_minion_data={"fake_key": "foo"}
288        )
289
290    def test_ext_pillar_with_extra_minion_data_val_elem(self):
291        opts = {
292            "optimization_order": [0, 1, 2],
293            "renderer": "json",
294            "renderer_blacklist": [],
295            "renderer_whitelist": [],
296            "state_top": "",
297            "pillar_roots": {"dev": [], "base": []},
298            "file_roots": {"dev": [], "base": []},
299            "extension_modules": "",
300            "pillarenv_from_saltenv": True,
301        }
302        mock_ext_pillar_func = MagicMock()
303        with patch(
304            "salt.loader.pillars",
305            MagicMock(return_value={"fake_ext_pillar": mock_ext_pillar_func}),
306        ):
307            pillar = salt.pillar.Pillar(
308                opts, {}, "mocked-minion", "dev", extra_minion_data={"fake_key": "foo"}
309            )
310        # ext pillar function doesn't have the extra_minion_data arg
311        with patch(
312            "salt.utils.args.get_function_argspec",
313            MagicMock(return_value=MagicMock(args=[])),
314        ):
315            pillar._external_pillar_data("fake_pillar", "bar", "fake_ext_pillar")
316        mock_ext_pillar_func.assert_called_once_with(
317            "mocked-minion", "fake_pillar", "bar"
318        )
319        # ext pillar function has the extra_minion_data arg
320        mock_ext_pillar_func.reset_mock()
321        with patch(
322            "salt.utils.args.get_function_argspec",
323            MagicMock(return_value=MagicMock(args=["extra_minion_data"])),
324        ):
325            pillar._external_pillar_data("fake_pillar", "bar", "fake_ext_pillar")
326        mock_ext_pillar_func.assert_called_once_with(
327            "mocked-minion", "fake_pillar", "bar", extra_minion_data={"fake_key": "foo"}
328        )
329
330    def test_ext_pillar_first(self):
331        """
332        test when using ext_pillar and ext_pillar_first
333        """
334        opts = {
335            "optimization_order": [0, 1, 2],
336            "renderer": "yaml",
337            "renderer_blacklist": [],
338            "renderer_whitelist": [],
339            "state_top": "",
340            "pillar_roots": [],
341            "extension_modules": "",
342            "saltenv": "base",
343            "file_roots": [],
344            "ext_pillar_first": True,
345        }
346        grains = {
347            "os": "Ubuntu",
348            "os_family": "Debian",
349            "oscodename": "raring",
350            "osfullname": "Ubuntu",
351            "osrelease": "13.04",
352            "kernel": "Linux",
353        }
354
355        tempdir = tempfile.mkdtemp(dir=RUNTIME_VARS.TMP)
356        try:
357            sls_files = self._setup_test_topfile_sls_pillar_match(
358                tempdir,
359            )
360            fc_mock = MockFileclient(
361                cache_file=sls_files["top"]["dest"],
362                list_states=["top", "ssh", "ssh.minion", "generic", "generic.minion"],
363                get_state=sls_files,
364            )
365            with patch.object(
366                salt.fileclient, "get_file_client", MagicMock(return_value=fc_mock)
367            ), patch(
368                "salt.pillar.Pillar.ext_pillar",
369                MagicMock(
370                    return_value=(
371                        {"id": "minion", "phase": "alpha", "role": "database"},
372                        [],
373                    )
374                ),
375            ):
376                pillar = salt.pillar.Pillar(opts, grains, "mocked-minion", "base")
377                self.assertEqual(pillar.compile_pillar()["generic"]["key1"], "value1")
378        finally:
379            shutil.rmtree(tempdir, ignore_errors=True)
380
381    def test_dynamic_pillarenv(self):
382        opts = {
383            "optimization_order": [0, 1, 2],
384            "renderer": "json",
385            "renderer_blacklist": [],
386            "renderer_whitelist": [],
387            "state_top": "",
388            "pillar_roots": {
389                "__env__": ["/srv/pillar/__env__"],
390                "base": ["/srv/pillar/base"],
391            },
392            "file_roots": {"base": ["/srv/salt/base"], "dev": ["/svr/salt/dev"]},
393            "extension_modules": "",
394        }
395        pillar = salt.pillar.Pillar(opts, {}, "mocked-minion", "base", pillarenv="dev")
396        self.assertEqual(
397            pillar.opts["pillar_roots"],
398            {"base": ["/srv/pillar/base"], "dev": ["/srv/pillar/__env__"]},
399        )
400
401    def test_ignored_dynamic_pillarenv(self):
402        opts = {
403            "optimization_order": [0, 1, 2],
404            "renderer": "json",
405            "renderer_blacklist": [],
406            "renderer_whitelist": [],
407            "state_top": "",
408            "pillar_roots": {
409                "__env__": ["/srv/pillar/__env__"],
410                "base": ["/srv/pillar/base"],
411            },
412            "file_roots": {"base": ["/srv/salt/base"], "dev": ["/svr/salt/dev"]},
413            "extension_modules": "",
414        }
415        pillar = salt.pillar.Pillar(opts, {}, "mocked-minion", "base", pillarenv="base")
416        self.assertEqual(pillar.opts["pillar_roots"], {"base": ["/srv/pillar/base"]})
417
418    @patch("salt.fileclient.Client.list_states")
419    def test_malformed_pillar_sls(self, mock_list_states):
420        with patch("salt.pillar.compile_template") as compile_template:
421            opts = {
422                "optimization_order": [0, 1, 2],
423                "renderer": "json",
424                "renderer_blacklist": [],
425                "renderer_whitelist": [],
426                "state_top": "",
427                "pillar_roots": [],
428                "file_roots": [],
429                "extension_modules": "",
430            }
431            grains = {
432                "os": "Ubuntu",
433                "os_family": "Debian",
434                "oscodename": "raring",
435                "osfullname": "Ubuntu",
436                "osrelease": "13.04",
437                "kernel": "Linux",
438            }
439
440            mock_list_states.return_value = ["foo", "blah"]
441            pillar = salt.pillar.Pillar(opts, grains, "mocked-minion", "base")
442            # Mock getting the proper template files
443            pillar.client.get_state = MagicMock(
444                return_value={
445                    "dest": "/path/to/pillar/files/foo.sls",
446                    "source": "salt://foo.sls",
447                }
448            )
449
450            # Template compilation returned a string
451            compile_template.return_value = "BAHHH"
452            self.assertEqual(
453                pillar.render_pillar({"base": ["foo.sls"]}),
454                ({}, ["SLS 'foo.sls' does not render to a dictionary"]),
455            )
456
457            # Template compilation returned a list
458            compile_template.return_value = ["BAHHH"]
459            self.assertEqual(
460                pillar.render_pillar({"base": ["foo.sls"]}),
461                ({}, ["SLS 'foo.sls' does not render to a dictionary"]),
462            )
463
464            # Template compilation returned a dictionary, which is what's expected
465            compile_template.return_value = {"foo": "bar"}
466            self.assertEqual(
467                pillar.render_pillar({"base": ["foo.sls"]}), ({"foo": "bar"}, [])
468            )
469
470            # Test improper includes
471            compile_template.side_effect = [
472                {"foo": "bar", "include": "blah"},
473                {"foo2": "bar2"},
474            ]
475            self.assertEqual(
476                pillar.render_pillar({"base": ["foo.sls"]}),
477                (
478                    {"foo": "bar", "include": "blah"},
479                    ["Include Declaration in SLS 'foo.sls' is not formed as a list"],
480                ),
481            )
482
483            # Test includes as a list, which is what's expected
484            compile_template.side_effect = [
485                {"foo": "bar", "include": ["blah"]},
486                {"foo2": "bar2"},
487            ]
488            self.assertEqual(
489                pillar.render_pillar({"base": ["foo.sls"]}),
490                ({"foo": "bar", "foo2": "bar2"}, []),
491            )
492
493            # Test includes as a list overriding data
494            compile_template.side_effect = [
495                {"foo": "bar", "include": ["blah"]},
496                {"foo": "bar2"},
497            ]
498            self.assertEqual(
499                pillar.render_pillar({"base": ["foo.sls"]}), ({"foo": "bar"}, [])
500            )
501
502            # Test includes using empty key directive
503            compile_template.side_effect = [
504                {"foo": "bar", "include": [{"blah": {"key": ""}}]},
505                {"foo": "bar2"},
506            ]
507            self.assertEqual(
508                pillar.render_pillar({"base": ["foo.sls"]}), ({"foo": "bar"}, [])
509            )
510
511            # Test includes using simple non-nested key
512            compile_template.side_effect = [
513                {"foo": "bar", "include": [{"blah": {"key": "nested"}}]},
514                {"foo": "bar2"},
515            ]
516            self.assertEqual(
517                pillar.render_pillar({"base": ["foo.sls"]}),
518                ({"foo": "bar", "nested": {"foo": "bar2"}}, []),
519            )
520
521            # Test includes using nested key
522            compile_template.side_effect = [
523                {"foo": "bar", "include": [{"blah": {"key": "nested:level"}}]},
524                {"foo": "bar2"},
525            ]
526            self.assertEqual(
527                pillar.render_pillar({"base": ["foo.sls"]}),
528                ({"foo": "bar", "nested": {"level": {"foo": "bar2"}}}, []),
529            )
530
531    def test_includes_override_sls(self):
532        opts = {
533            "optimization_order": [0, 1, 2],
534            "renderer": "json",
535            "renderer_blacklist": [],
536            "renderer_whitelist": [],
537            "state_top": "",
538            "pillar_roots": {},
539            "file_roots": {},
540            "extension_modules": "",
541        }
542        grains = {
543            "os": "Ubuntu",
544            "os_family": "Debian",
545            "oscodename": "raring",
546            "osfullname": "Ubuntu",
547            "osrelease": "13.04",
548            "kernel": "Linux",
549        }
550        with patch("salt.pillar.compile_template") as compile_template, patch.object(
551            salt.pillar.Pillar,
552            "_Pillar__gather_avail",
553            MagicMock(return_value={"base": ["blah", "foo"]}),
554        ):
555
556            # Test with option set to True
557            opts["pillar_includes_override_sls"] = True
558            pillar = salt.pillar.Pillar(opts, grains, "mocked-minion", "base")
559            # Mock getting the proper template files
560            pillar.client.get_state = MagicMock(
561                return_value={
562                    "dest": "/path/to/pillar/files/foo.sls",
563                    "source": "salt://foo.sls",
564                }
565            )
566
567            compile_template.side_effect = [
568                {"foo": "bar", "include": ["blah"]},
569                {"foo": "bar2"},
570            ]
571            self.assertEqual(
572                pillar.render_pillar({"base": ["foo.sls"]}), ({"foo": "bar2"}, [])
573            )
574
575            # Test with option set to False
576            opts["pillar_includes_override_sls"] = False
577            pillar = salt.pillar.Pillar(opts, grains, "mocked-minion", "base")
578            # Mock getting the proper template files
579            pillar.client.get_state = MagicMock(
580                return_value={
581                    "dest": "/path/to/pillar/files/foo.sls",
582                    "source": "salt://foo.sls",
583                }
584            )
585
586            compile_template.side_effect = [
587                {"foo": "bar", "include": ["blah"]},
588                {"foo": "bar2"},
589            ]
590            self.assertEqual(
591                pillar.render_pillar({"base": ["foo.sls"]}), ({"foo": "bar"}, [])
592            )
593
594    def test_topfile_order(self):
595        opts = {
596            "optimization_order": [0, 1, 2],
597            "renderer": "yaml",
598            "renderer_blacklist": [],
599            "renderer_whitelist": [],
600            "state_top": "",
601            "pillar_roots": [],
602            "extension_modules": "",
603            "saltenv": "base",
604            "file_roots": [],
605        }
606        grains = {
607            "os": "Ubuntu",
608            "os_family": "Debian",
609            "oscodename": "raring",
610            "osfullname": "Ubuntu",
611            "osrelease": "13.04",
612            "kernel": "Linux",
613        }
614
615        def _run_test(nodegroup_order, glob_order, expected):
616            tempdir = tempfile.mkdtemp(dir=RUNTIME_VARS.TMP)
617            try:
618                sls_files = self._setup_test_topfile_sls(
619                    tempdir, nodegroup_order, glob_order
620                )
621                fc_mock = MockFileclient(
622                    cache_file=sls_files["top"]["dest"],
623                    list_states=[
624                        "top",
625                        "ssh",
626                        "ssh.minion",
627                        "generic",
628                        "generic.minion",
629                    ],
630                    get_state=sls_files,
631                )
632                with patch.object(
633                    salt.fileclient, "get_file_client", MagicMock(return_value=fc_mock)
634                ):
635                    pillar = salt.pillar.Pillar(opts, grains, "mocked-minion", "base")
636                    # Make sure that confirm_top.confirm_top returns True
637                    pillar.matchers["confirm_top.confirm_top"] = lambda *x, **y: True
638                    self.assertEqual(pillar.compile_pillar()["ssh"], expected)
639            finally:
640                shutil.rmtree(tempdir, ignore_errors=True)
641
642        # test case where glob match happens second and therefore takes
643        # precedence over nodegroup match.
644        _run_test(nodegroup_order=1, glob_order=2, expected="bar")
645        # test case where nodegroup match happens second and therefore takes
646        # precedence over glob match.
647        _run_test(nodegroup_order=2, glob_order=1, expected="foo")
648
649    def _setup_test_topfile_sls_pillar_match(self, tempdir):
650        # Write a simple topfile and two pillar state files
651        top_file = tempfile.NamedTemporaryFile(dir=tempdir, delete=False)
652        s = """
653base:
654  'phase:alpha':
655    - match: pillar
656    - generic
657"""
658        top_file.write(salt.utils.stringutils.to_bytes(s))
659        top_file.flush()
660        generic_file = tempfile.NamedTemporaryFile(dir=tempdir, delete=False)
661        generic_file.write(
662            b"""
663generic:
664    key1: value1
665"""
666        )
667        generic_file.flush()
668        return {
669            "top": {"path": "", "dest": top_file.name},
670            "generic": {"path": "", "dest": generic_file.name},
671        }
672
673    def _setup_test_topfile_sls(self, tempdir, nodegroup_order, glob_order):
674        # Write a simple topfile and two pillar state files
675        top_file = tempfile.NamedTemporaryFile(dir=tempdir, delete=False)
676        s = """
677base:
678    group:
679        - match: nodegroup
680        - order: {nodegroup_order}
681        - ssh
682        - generic
683    '*':
684        - generic
685    minion:
686        - order: {glob_order}
687        - ssh.minion
688        - generic.minion
689""".format(
690            nodegroup_order=nodegroup_order, glob_order=glob_order
691        )
692        top_file.write(salt.utils.stringutils.to_bytes(s))
693        top_file.flush()
694        ssh_file = tempfile.NamedTemporaryFile(dir=tempdir, delete=False)
695        ssh_file.write(
696            b"""
697ssh:
698    foo
699"""
700        )
701        ssh_file.flush()
702        ssh_minion_file = tempfile.NamedTemporaryFile(dir=tempdir, delete=False)
703        ssh_minion_file.write(
704            b"""
705ssh:
706    bar
707"""
708        )
709        ssh_minion_file.flush()
710        generic_file = tempfile.NamedTemporaryFile(dir=tempdir, delete=False)
711        generic_file.write(
712            b"""
713generic:
714    key1:
715      - value1
716      - value2
717    key2:
718        sub_key1: []
719"""
720        )
721        generic_file.flush()
722        generic_minion_file = tempfile.NamedTemporaryFile(dir=tempdir, delete=False)
723        generic_minion_file.write(
724            b"""
725generic:
726    key1:
727      - value3
728    key2:
729        sub_key2: []
730"""
731        )
732        generic_minion_file.flush()
733
734        return {
735            "top": {"path": "", "dest": top_file.name},
736            "ssh": {"path": "", "dest": ssh_file.name},
737            "ssh.minion": {"path": "", "dest": ssh_minion_file.name},
738            "generic": {"path": "", "dest": generic_file.name},
739            "generic.minion": {"path": "", "dest": generic_minion_file.name},
740        }
741
742    @with_tempdir()
743    def test_include(self, tempdir):
744        opts = {
745            "optimization_order": [0, 1, 2],
746            "renderer": "yaml",
747            "renderer_blacklist": [],
748            "renderer_whitelist": [],
749            "state_top": "",
750            "pillar_roots": [],
751            "extension_modules": "",
752            "saltenv": "base",
753            "file_roots": [],
754        }
755        grains = {
756            "os": "Ubuntu",
757            "os_family": "Debian",
758            "oscodename": "raring",
759            "osfullname": "Ubuntu",
760            "osrelease": "13.04",
761            "kernel": "Linux",
762        }
763        sls_files = self._setup_test_include_sls(tempdir)
764        fc_mock = MockFileclient(
765            cache_file=sls_files["top"]["dest"],
766            get_state=sls_files,
767            list_states=[
768                "top",
769                "test.init",
770                "test.sub1",
771                "test.sub2",
772                "test.sub_wildcard_1",
773                "test.sub_with_init_dot",
774                "test.sub.with.slashes",
775            ],
776        )
777        with patch.object(
778            salt.fileclient, "get_file_client", MagicMock(return_value=fc_mock)
779        ):
780            pillar = salt.pillar.Pillar(opts, grains, "minion", "base")
781            # Make sure that confirm_top.confirm_top returns True
782            pillar.matchers["confirm_top.confirm_top"] = lambda *x, **y: True
783            compiled_pillar = pillar.compile_pillar()
784            self.assertEqual(compiled_pillar["foo_wildcard"], "bar_wildcard")
785            self.assertEqual(compiled_pillar["foo1"], "bar1")
786            self.assertEqual(compiled_pillar["foo2"], "bar2")
787            self.assertEqual(compiled_pillar["sub_with_slashes"], "sub_slashes_worked")
788            self.assertEqual(
789                compiled_pillar["sub_init_dot"], "sub_with_init_dot_worked"
790            )
791
792    def _setup_test_include_sls(self, tempdir):
793        top_file = tempfile.NamedTemporaryFile(dir=tempdir, delete=False)
794        top_file.write(
795            b"""
796base:
797    '*':
798        - order: 1
799        - test.sub2
800    minion:
801        - order: 2
802        - test
803"""
804        )
805        top_file.flush()
806        init_sls = tempfile.NamedTemporaryFile(dir=tempdir, delete=False)
807        init_sls.write(
808            b"""
809include:
810   - test.sub1
811   - test.sub_wildcard*
812   - .test.sub_with_init_dot
813   - test/sub/with/slashes
814"""
815        )
816        init_sls.flush()
817        sub1_sls = tempfile.NamedTemporaryFile(dir=tempdir, delete=False)
818        sub1_sls.write(
819            b"""
820foo1:
821  bar1
822"""
823        )
824        sub1_sls.flush()
825        sub2_sls = tempfile.NamedTemporaryFile(dir=tempdir, delete=False)
826        sub2_sls.write(
827            b"""
828foo2:
829  bar2
830"""
831        )
832        sub2_sls.flush()
833
834        sub_wildcard_1_sls = tempfile.NamedTemporaryFile(dir=tempdir, delete=False)
835        sub_wildcard_1_sls.write(
836            b"""
837foo_wildcard:
838   bar_wildcard
839"""
840        )
841        sub_wildcard_1_sls.flush()
842
843        sub_with_init_dot_sls = tempfile.NamedTemporaryFile(dir=tempdir, delete=False)
844        sub_with_init_dot_sls.write(
845            b"""
846sub_init_dot:
847  sub_with_init_dot_worked
848"""
849        )
850        sub_with_init_dot_sls.flush()
851
852        sub_with_slashes_sls = tempfile.NamedTemporaryFile(dir=tempdir, delete=False)
853        sub_with_slashes_sls.write(
854            b"""
855sub_with_slashes:
856  sub_slashes_worked
857"""
858        )
859        sub_with_slashes_sls.flush()
860
861        return {
862            "top": {"path": "", "dest": top_file.name},
863            "test": {"path": "", "dest": init_sls.name},
864            "test.sub1": {"path": "", "dest": sub1_sls.name},
865            "test.sub2": {"path": "", "dest": sub2_sls.name},
866            "test.sub_wildcard_1": {"path": "", "dest": sub_wildcard_1_sls.name},
867            "test.sub_with_init_dot": {"path": "", "dest": sub_with_init_dot_sls.name},
868            "test.sub.with.slashes": {"path": "", "dest": sub_with_slashes_sls.name},
869        }
870
871    @with_tempdir()
872    def test_relative_include(self, tempdir):
873        join = os.path.join
874        with fopen(join(tempdir, "top.sls"), "w") as f:
875            print(
876                textwrap.dedent(
877                    """
878                    base:
879                      '*':
880                        - includer
881                        - simple_includer
882                        - includes.with.more.depth
883                """
884                ),
885                file=f,
886            )
887        includer_dir = join(tempdir, "includer")
888        os.makedirs(includer_dir)
889        with fopen(join(includer_dir, "init.sls"), "w") as f:
890            print(
891                textwrap.dedent(
892                    """
893                    include:
894                      - .this
895                      - includer.that
896                """
897                ),
898                file=f,
899            )
900        with fopen(join(includer_dir, "this.sls"), "w") as f:
901            print(
902                textwrap.dedent(
903                    """
904                    this:
905                        is all good
906                """
907                ),
908                file=f,
909            )
910        with fopen(join(includer_dir, "that.sls"), "w") as f:
911            print(
912                textwrap.dedent(
913                    """
914                    that:
915                        is also all good
916                """
917                ),
918                file=f,
919            )
920
921        with fopen(join(tempdir, "simple_includer.sls"), "w") as simpleincluder:
922            print(
923                textwrap.dedent(
924                    """
925                    include:
926                      - .simple
927                      - super_simple
928                """
929                ),
930                file=simpleincluder,
931            )
932        with fopen(join(tempdir, "simple.sls"), "w") as f:
933            print(
934                textwrap.dedent(
935                    """
936                    simple:
937                      simon
938                """
939                ),
940                file=f,
941            )
942        with fopen(join(tempdir, "super_simple.sls"), "w") as f:
943            print(
944                textwrap.dedent(
945                    """
946                    super simple:
947                      a caveman
948                """
949                ),
950                file=f,
951            )
952
953        depth_dir = join(tempdir, "includes", "with", "more")
954        os.makedirs(depth_dir)
955        with fopen(join(depth_dir, "depth.sls"), "w") as f:
956            print(
957                textwrap.dedent(
958                    """
959                    include:
960                      - .ramble
961                      - includes.with.more.doors
962
963                    mordor:
964                        has dark depths
965                """
966                ),
967                file=f,
968            )
969
970        with fopen(join(depth_dir, "ramble.sls"), "w") as f:
971            print(
972                textwrap.dedent(
973                    """
974                    found:
975                        my precious
976                """
977                ),
978                file=f,
979            )
980
981        with fopen(join(depth_dir, "doors.sls"), "w") as f:
982            print(
983                textwrap.dedent(
984                    """
985                    mojo:
986                        bad risin'
987                """
988                ),
989                file=f,
990            )
991        opts = {
992            "optimization_order": [0, 1, 2],
993            "renderer": "yaml",
994            "renderer_blacklist": [],
995            "renderer_whitelist": [],
996            "state_top": "top.sls",
997            "pillar_roots": {"base": [tempdir]},
998            "extension_modules": "",
999            "saltenv": "base",
1000            "file_roots": [],
1001            "file_ignore_regex": None,
1002            "file_ignore_glob": None,
1003        }
1004        grains = {
1005            "os": "Ubuntu",
1006            "os_family": "Debian",
1007            "oscodename": "raring",
1008            "osfullname": "Ubuntu",
1009            "osrelease": "13.04",
1010            "kernel": "Linux",
1011        }
1012        pillar = salt.pillar.Pillar(opts, grains, "minion", "base")
1013        # Make sure that confirm_top.confirm_top returns True
1014        pillar.matchers["confirm_top.confirm_top"] = lambda *x, **y: True
1015
1016        # Act
1017        compiled_pillar = pillar.compile_pillar()
1018
1019        # Assert
1020        self.assertEqual(compiled_pillar["this"], "is all good")
1021        self.assertEqual(compiled_pillar["that"], "is also all good")
1022        self.assertEqual(compiled_pillar["simple"], "simon")
1023        self.assertEqual(compiled_pillar["super simple"], "a caveman")
1024        self.assertEqual(compiled_pillar["mordor"], "has dark depths")
1025        self.assertEqual(compiled_pillar["found"], "my precious")
1026        self.assertEqual(compiled_pillar["mojo"], "bad risin'")
1027
1028    @with_tempdir()
1029    def test_missing_include(self, tempdir):
1030        opts = {
1031            "optimization_order": [0, 1, 2],
1032            "renderer": "yaml",
1033            "renderer_blacklist": [],
1034            "renderer_whitelist": [],
1035            "state_top": "top.sls",
1036            "pillar_roots": {"base": [tempdir]},
1037            "extension_modules": "",
1038            "saltenv": "base",
1039            "file_roots": [],
1040            "file_ignore_regex": None,
1041            "file_ignore_glob": None,
1042        }
1043        grains = {
1044            "os": "Ubuntu",
1045            "os_family": "Debian",
1046            "oscodename": "raring",
1047            "osfullname": "Ubuntu",
1048            "osrelease": "13.04",
1049            "kernel": "Linux",
1050        }
1051
1052        join = os.path.join
1053        with fopen(join(tempdir, "top.sls"), "w") as f:
1054            print(
1055                textwrap.dedent(
1056                    """
1057                    base:
1058                      '*':
1059                        - simple_include
1060                """
1061                ),
1062                file=f,
1063            )
1064        include_dir = join(tempdir, "simple_include")
1065        os.makedirs(include_dir)
1066        with fopen(join(include_dir, "init.sls"), "w") as f:
1067            print(
1068                textwrap.dedent(
1069                    """
1070                    include:
1071                      - simple_include.missing_include
1072                    simple_include: is ok
1073                    """
1074                ),
1075                file=f,
1076            )
1077
1078        pillar = salt.pillar.Pillar(opts, grains, "minion", "base")
1079        # Make sure that confirm_top.confirm_top returns True
1080        pillar.matchers["confirm_top.confirm_top"] = lambda *x, **y: True
1081
1082        # Act
1083        compiled_pillar = pillar.compile_pillar()
1084
1085        # Assert
1086        self.assertEqual(compiled_pillar["simple_include"], "is ok")
1087        self.assertTrue("_errors" in compiled_pillar)
1088        self.assertTrue(
1089            "simple_include.missing_include" in compiled_pillar["_errors"][0]
1090        )
1091
1092
1093@patch("salt.transport.client.ReqChannel.factory", MagicMock())
1094class RemotePillarTestCase(TestCase):
1095    """
1096    Tests for instantiating a RemotePillar in salt.pillar
1097    """
1098
1099    def setUp(self):
1100        self.grains = {}
1101
1102    def tearDown(self):
1103        for attr in ("grains",):
1104            try:
1105                delattr(self, attr)
1106            except AttributeError:
1107                continue
1108
1109    def test_get_opts_in_pillar_override_call(self):
1110        mock_get_extra_minion_data = MagicMock(return_value={})
1111        with patch(
1112            "salt.pillar.RemotePillarMixin.get_ext_pillar_extra_minion_data",
1113            mock_get_extra_minion_data,
1114        ):
1115
1116            salt.pillar.RemotePillar({}, self.grains, "mocked-minion", "dev")
1117        mock_get_extra_minion_data.assert_called_once_with({"saltenv": "dev"})
1118
1119    def test_multiple_keys_in_opts_added_to_pillar(self):
1120        opts = {
1121            "renderer": "json",
1122            "path_to_add": "fake_data",
1123            "path_to_add2": {"fake_data2": ["fake_data3", "fake_data4"]},
1124            "pass_to_ext_pillars": ["path_to_add", "path_to_add2"],
1125        }
1126        pillar = salt.pillar.RemotePillar(opts, self.grains, "mocked-minion", "dev")
1127        self.assertEqual(
1128            pillar.extra_minion_data,
1129            {
1130                "path_to_add": "fake_data",
1131                "path_to_add2": {"fake_data2": ["fake_data3", "fake_data4"]},
1132            },
1133        )
1134
1135    def test_subkey_in_opts_added_to_pillar(self):
1136        opts = {
1137            "renderer": "json",
1138            "path_to_add": "fake_data",
1139            "path_to_add2": {
1140                "fake_data5": "fake_data6",
1141                "fake_data2": ["fake_data3", "fake_data4"],
1142            },
1143            "pass_to_ext_pillars": ["path_to_add2:fake_data5"],
1144        }
1145        pillar = salt.pillar.RemotePillar(opts, self.grains, "mocked-minion", "dev")
1146        self.assertEqual(
1147            pillar.extra_minion_data, {"path_to_add2": {"fake_data5": "fake_data6"}}
1148        )
1149
1150    def test_non_existent_leaf_opt_in_add_to_pillar(self):
1151        opts = {
1152            "renderer": "json",
1153            "path_to_add": "fake_data",
1154            "path_to_add2": {
1155                "fake_data5": "fake_data6",
1156                "fake_data2": ["fake_data3", "fake_data4"],
1157            },
1158            "pass_to_ext_pillars": ["path_to_add2:fake_data_non_exist"],
1159        }
1160        pillar = salt.pillar.RemotePillar(opts, self.grains, "mocked-minion", "dev")
1161        self.assertEqual(pillar.pillar_override, {})
1162
1163    def test_non_existent_intermediate_opt_in_add_to_pillar(self):
1164        opts = {
1165            "renderer": "json",
1166            "path_to_add": "fake_data",
1167            "path_to_add2": {
1168                "fake_data5": "fake_data6",
1169                "fake_data2": ["fake_data3", "fake_data4"],
1170            },
1171            "pass_to_ext_pillars": ["path_to_add_no_exist"],
1172        }
1173        pillar = salt.pillar.RemotePillar(opts, self.grains, "mocked-minion", "dev")
1174        self.assertEqual(pillar.pillar_override, {})
1175
1176    def test_malformed_add_to_pillar(self):
1177        opts = {
1178            "renderer": "json",
1179            "path_to_add": "fake_data",
1180            "path_to_add2": {
1181                "fake_data5": "fake_data6",
1182                "fake_data2": ["fake_data3", "fake_data4"],
1183            },
1184            "pass_to_ext_pillars": MagicMock(),
1185        }
1186        with self.assertRaises(salt.exceptions.SaltClientError) as excinfo:
1187            salt.pillar.RemotePillar(opts, self.grains, "mocked-minion", "dev")
1188        self.assertEqual(
1189            excinfo.exception.strerror, "'pass_to_ext_pillars' config is malformed."
1190        )
1191
1192    def test_pillar_send_extra_minion_data_from_config(self):
1193        opts = {
1194            "renderer": "json",
1195            "pillarenv": "fake_pillar_env",
1196            "path_to_add": "fake_data",
1197            "path_to_add2": {
1198                "fake_data5": "fake_data6",
1199                "fake_data2": ["fake_data3", "fake_data4"],
1200            },
1201            "pass_to_ext_pillars": ["path_to_add"],
1202        }
1203        mock_channel = MagicMock(
1204            crypted_transfer_decode_dictentry=MagicMock(return_value={})
1205        )
1206        with patch(
1207            "salt.transport.client.ReqChannel.factory",
1208            MagicMock(return_value=mock_channel),
1209        ):
1210            pillar = salt.pillar.RemotePillar(
1211                opts, self.grains, "mocked_minion", "fake_env"
1212            )
1213
1214        ret = pillar.compile_pillar()
1215        self.assertEqual(pillar.channel, mock_channel)
1216        mock_channel.crypted_transfer_decode_dictentry.assert_called_once_with(
1217            {
1218                "cmd": "_pillar",
1219                "ver": "2",
1220                "id": "mocked_minion",
1221                "grains": {},
1222                "saltenv": "fake_env",
1223                "pillarenv": "fake_pillar_env",
1224                "pillar_override": {},
1225                "extra_minion_data": {"path_to_add": "fake_data"},
1226            },
1227            dictkey="pillar",
1228        )
1229
1230    def test_pillar_file_client_master_remote(self):
1231        """
1232        Test condition where local file_client and use_master_when_local option
1233        returns a remote file client.
1234        """
1235        mocked_minion = MagicMock()
1236        opts = {
1237            "file_client": "local",
1238            "use_master_when_local": True,
1239            "pillar_cache": None,
1240        }
1241        pillar = salt.pillar.get_pillar(opts, self.grains, mocked_minion)
1242        self.assertEqual(type(pillar), salt.pillar.RemotePillar)
1243        self.assertNotEqual(type(pillar), salt.pillar.PillarCache)
1244
1245
1246@patch("salt.transport.client.AsyncReqChannel.factory", MagicMock())
1247class AsyncRemotePillarTestCase(TestCase):
1248    """
1249    Tests for instantiating a AsyncRemotePillar in salt.pillar
1250    """
1251
1252    def setUp(self):
1253        self.grains = {}
1254
1255    def tearDown(self):
1256        for attr in ("grains",):
1257            try:
1258                delattr(self, attr)
1259            except AttributeError:
1260                continue
1261
1262    def test_get_opts_in_pillar_override_call(self):
1263        mock_get_extra_minion_data = MagicMock(return_value={})
1264        with patch(
1265            "salt.pillar.RemotePillarMixin.get_ext_pillar_extra_minion_data",
1266            mock_get_extra_minion_data,
1267        ):
1268
1269            salt.pillar.RemotePillar({}, self.grains, "mocked-minion", "dev")
1270        mock_get_extra_minion_data.assert_called_once_with({"saltenv": "dev"})
1271
1272    def test_pillar_send_extra_minion_data_from_config(self):
1273        opts = {
1274            "renderer": "json",
1275            "pillarenv": "fake_pillar_env",
1276            "path_to_add": "fake_data",
1277            "path_to_add2": {
1278                "fake_data5": "fake_data6",
1279                "fake_data2": ["fake_data3", "fake_data4"],
1280            },
1281            "pass_to_ext_pillars": ["path_to_add"],
1282        }
1283        mock_channel = MagicMock(
1284            crypted_transfer_decode_dictentry=MagicMock(return_value={})
1285        )
1286        with patch(
1287            "salt.transport.client.AsyncReqChannel.factory",
1288            MagicMock(return_value=mock_channel),
1289        ):
1290            pillar = salt.pillar.RemotePillar(
1291                opts, self.grains, "mocked_minion", "fake_env"
1292            )
1293
1294        ret = pillar.compile_pillar()
1295        mock_channel.crypted_transfer_decode_dictentry.assert_called_once_with(
1296            {
1297                "cmd": "_pillar",
1298                "ver": "2",
1299                "id": "mocked_minion",
1300                "grains": {},
1301                "saltenv": "fake_env",
1302                "pillarenv": "fake_pillar_env",
1303                "pillar_override": {},
1304                "extra_minion_data": {"path_to_add": "fake_data"},
1305            },
1306            dictkey="pillar",
1307        )
1308
1309
1310@patch("salt.transport.client.ReqChannel.factory", MagicMock())
1311class PillarCacheTestCase(TestCase):
1312    """
1313    Tests for instantiating a PillarCache in salt.pillar
1314    """
1315
1316    def setUp(self):
1317        self.grains = {}
1318
1319    @classmethod
1320    def setUpClass(cls):
1321        cls.mock_master_default_opts = salt.config.DEFAULT_MASTER_OPTS.copy()
1322        cls.mock_master_default_opts["cachedir"] = tempfile.mkdtemp(
1323            dir=RUNTIME_VARS.TMP
1324        )
1325
1326    def tearDown(self):
1327        for attr in ("grains",):
1328            try:
1329                delattr(self, attr)
1330            except AttributeError:
1331                continue
1332
1333    def test_compile_pillar_disk_cache(self):
1334        self.mock_master_default_opts.update(
1335            {"pillar_cache_backend": "disk", "pillar_cache_ttl": 3600}
1336        )
1337
1338        pillar = salt.pillar.PillarCache(
1339            self.mock_master_default_opts,
1340            self.grains,
1341            "mocked_minion",
1342            "fake_env",
1343            pillarenv="base",
1344        )
1345
1346        with patch("salt.utils.cache.CacheDisk._write", MagicMock()):
1347            with patch(
1348                "salt.pillar.PillarCache.fetch_pillar",
1349                side_effect=[{"foo": "bar"}, {"foo": "baz"}],
1350            ):
1351                # Run once for pillarenv base
1352                ret = pillar.compile_pillar()
1353                expected_cache = {"mocked_minion": {"base": {"foo": "bar"}}}
1354                self.assertEqual(pillar.cache._dict, expected_cache)
1355
1356                # Run a second time for pillarenv base
1357                ret = pillar.compile_pillar()
1358                expected_cache = {"mocked_minion": {"base": {"foo": "bar"}}}
1359                self.assertEqual(pillar.cache._dict, expected_cache)
1360
1361                # Change the pillarenv
1362                pillar.pillarenv = "dev"
1363
1364                # Run once for pillarenv dev
1365                ret = pillar.compile_pillar()
1366                expected_cache = {
1367                    "mocked_minion": {"base": {"foo": "bar"}, "dev": {"foo": "baz"}}
1368                }
1369                self.assertEqual(pillar.cache._dict, expected_cache)
1370
1371                # Run a second time for pillarenv dev
1372                ret = pillar.compile_pillar()
1373                expected_cache = {
1374                    "mocked_minion": {"base": {"foo": "bar"}, "dev": {"foo": "baz"}}
1375                }
1376                self.assertEqual(pillar.cache._dict, expected_cache)
1377
1378    def test_compile_pillar_memory_cache(self):
1379        self.mock_master_default_opts.update(
1380            {"pillar_cache_backend": "memory", "pillar_cache_ttl": 3600}
1381        )
1382
1383        pillar = salt.pillar.PillarCache(
1384            self.mock_master_default_opts,
1385            self.grains,
1386            "mocked_minion",
1387            "fake_env",
1388            pillarenv="base",
1389        )
1390
1391        with patch(
1392            "salt.pillar.PillarCache.fetch_pillar",
1393            side_effect=[{"foo": "bar"}, {"foo": "baz"}],
1394        ):
1395            # Run once for pillarenv base
1396            ret = pillar.compile_pillar()
1397            expected_cache = {"base": {"foo": "bar"}}
1398            self.assertIn("mocked_minion", pillar.cache)
1399            self.assertEqual(pillar.cache["mocked_minion"], expected_cache)
1400
1401            # Run a second time for pillarenv base
1402            ret = pillar.compile_pillar()
1403            expected_cache = {"base": {"foo": "bar"}}
1404            self.assertIn("mocked_minion", pillar.cache)
1405            self.assertEqual(pillar.cache["mocked_minion"], expected_cache)
1406
1407            # Change the pillarenv
1408            pillar.pillarenv = "dev"
1409
1410            # Run once for pillarenv dev
1411            ret = pillar.compile_pillar()
1412            expected_cache = {"base": {"foo": "bar"}, "dev": {"foo": "baz"}}
1413            self.assertIn("mocked_minion", pillar.cache)
1414            self.assertEqual(pillar.cache["mocked_minion"], expected_cache)
1415
1416            # Run a second time for pillarenv dev
1417            ret = pillar.compile_pillar()
1418            expected_cache = {"base": {"foo": "bar"}, "dev": {"foo": "baz"}}
1419            self.assertIn("mocked_minion", pillar.cache)
1420            self.assertEqual(pillar.cache["mocked_minion"], expected_cache)
1421