1import io 2import os 3import os.path 4 5import attr 6import pytest 7import salt.config 8import salt.loader 9from salt.exceptions import SaltRenderError 10 11REQUISITES = ["require", "require_in", "use", "use_in", "watch", "watch_in"] 12 13 14@attr.s 15class Renderer: 16 tmp_path = attr.ib() 17 18 def __call__( 19 self, content, sls="", saltenv="base", argline="-G yaml . jinja", **kws 20 ): 21 root_dir = self.tmp_path 22 state_tree_dir = self.tmp_path / "state_tree" 23 cache_dir = self.tmp_path / "cachedir" 24 state_tree_dir.mkdir() 25 cache_dir.mkdir() 26 config = salt.config.minion_config(None) 27 config["root_dir"] = str(root_dir) 28 config["state_events"] = False 29 config["id"] = "match" 30 config["file_client"] = "local" 31 config["file_roots"] = dict(base=[str(state_tree_dir)]) 32 config["cachedir"] = str(cache_dir) 33 config["test"] = False 34 _renderers = salt.loader.render(config, {"config.get": lambda a, b: False}) 35 return _renderers["stateconf"]( 36 io.StringIO(content), 37 saltenv=saltenv, 38 sls=sls, 39 argline=argline, 40 renderers=salt.loader.render(config, {}), 41 **kws 42 ) 43 44 45@pytest.fixture 46def renderer(tmp_path): 47 return Renderer(tmp_path) 48 49 50def test_state_config(renderer): 51 result = renderer( 52 """ 53.sls_params: 54 stateconf.set: 55 - name1: value1 56 - name2: value2 57 58.extra: 59 stateconf: 60 - set 61 - name: value 62 63# --- end of state config --- 64 65test: 66 cmd.run: 67 - name: echo name1={{sls_params.name1}} name2={{sls_params.name2}} {{extra.name}} 68 - cwd: / 69""", 70 sls="test", 71 ) 72 assert len(result) == 3 73 assert "test::sls_params" in result and "test" in result 74 assert "test::extra" in result 75 assert ( 76 result["test"]["cmd.run"][0]["name"] == "echo name1=value1 name2=value2 value" 77 ) 78 79 80def test_sls_dir(renderer): 81 result = renderer( 82 """ 83test: 84 cmd.run: 85 - name: echo sls_dir={{sls_dir}} 86 - cwd: / 87""", 88 sls="path.to.sls", 89 ) 90 assert result["test"]["cmd.run"][0]["name"] == "echo sls_dir=path{}to".format( 91 os.sep 92 ) 93 94 95def test_states_declared_with_shorthand_no_args(renderer): 96 result = renderer( 97 """ 98test: 99 cmd.run: 100 - name: echo testing 101 - cwd: / 102test1: 103 pkg.installed 104test2: 105 user.present 106""" 107 ) 108 assert len(result) == 3 109 for args in (result["test1"]["pkg.installed"], result["test2"]["user.present"]): 110 assert isinstance(args, list) 111 assert len(args) == 0 112 assert result["test"]["cmd.run"][0]["name"] == "echo testing" 113 114 115def test_adding_state_name_arg_for_dot_state_id(renderer): 116 result = renderer( 117 """ 118.test: 119 pkg.installed: 120 - cwd: / 121.test2: 122 pkg.installed: 123 - name: vim 124""", 125 sls="test", 126 ) 127 assert result["test::test"]["pkg.installed"][0]["name"] == "test" 128 assert result["test::test2"]["pkg.installed"][0]["name"] == "vim" 129 130 131def test_state_prefix(renderer): 132 result = renderer( 133 """ 134.test: 135 cmd.run: 136 - name: echo renamed 137 - cwd: / 138 139state_id: 140 cmd: 141 - run 142 - name: echo not renamed 143 - cwd: / 144""", 145 sls="test", 146 ) 147 assert len(result) == 2 148 assert "test::test" in result 149 assert "state_id" in result 150 151 152@pytest.mark.parametrize("req", REQUISITES) 153def test_dot_state_id_in_requisites(req, renderer): 154 result = renderer( 155 """ 156.test: 157 cmd.run: 158 - name: echo renamed 159 - cwd: / 160 161state_id: 162 cmd.run: 163 - name: echo not renamed 164 - cwd: / 165 - {}: 166 - cmd: .test 167 168""".format( 169 req 170 ), 171 sls="test", 172 ) 173 assert len(result) == 2 174 assert "test::test" in result 175 assert "state_id" in result 176 assert result["state_id"]["cmd.run"][2][req][0]["cmd"] == "test::test" 177 178 179@pytest.mark.parametrize("req", REQUISITES) 180def test_relative_include_with_requisites(req, renderer): 181 result = renderer( 182 """ 183include: 184 - some.helper 185 - .utils 186 187state_id: 188 cmd.run: 189 - name: echo test 190 - cwd: / 191 - {}: 192 - cmd: .utils::some_state 193""".format( 194 req 195 ), 196 sls="test.work", 197 ) 198 assert result["include"][1] == {"base": "test.utils"} 199 assert result["state_id"]["cmd.run"][2][req][0]["cmd"] == "test.utils::some_state" 200 201 202def test_relative_include_and_extend(renderer): 203 result = renderer( 204 """ 205include: 206 - some.helper 207 - .utils 208 209extend: 210 .utils::some_state: 211 cmd.run: 212 - name: echo overridden 213 """, 214 sls="test.work", 215 ) 216 assert "test.utils::some_state" in result["extend"] 217 218 219@pytest.mark.parametrize("req", REQUISITES) 220def test_multilevel_relative_include_with_requisites(req, renderer): 221 result = renderer( 222 """ 223include: 224 - .shared 225 - ..utils 226 - ...helper 227 228state_id: 229 cmd.run: 230 - name: echo test 231 - cwd: / 232 - {}: 233 - cmd: ..utils::some_state 234""".format( 235 req 236 ), 237 sls="test.nested.work", 238 ) 239 assert result["include"][0] == {"base": "test.nested.shared"} 240 assert result["include"][1] == {"base": "test.utils"} 241 assert result["include"][2] == {"base": "helper"} 242 assert result["state_id"]["cmd.run"][2][req][0]["cmd"] == "test.utils::some_state" 243 244 245def test_multilevel_relative_include_beyond_top_level(renderer): 246 pytest.raises( 247 SaltRenderError, 248 renderer, 249 """ 250include: 251 - ...shared 252""", 253 sls="test.work", 254 ) 255 256 257def test_start_state_generation(renderer): 258 result = renderer( 259 """ 260A: 261 cmd.run: 262 - name: echo hello 263 - cwd: / 264B: 265 cmd.run: 266 - name: echo world 267 - cwd: / 268""", 269 sls="test", 270 argline="-so yaml . jinja", 271 ) 272 assert len(result) == 4 273 assert result["test::start"]["stateconf.set"][0]["require_in"][0]["cmd"] == "A" 274 275 276def test_goal_state_generation(renderer): 277 result = renderer( 278 """ 279{% for sid in "ABCDE": %} 280{{sid}}: 281 cmd.run: 282 - name: echo this is {{sid}} 283 - cwd: / 284{% endfor %} 285 286""", 287 sls="test.goalstate", 288 argline="yaml . jinja", 289 ) 290 assert len(result) == len("ABCDE") + 1 291 292 reqs = result["test.goalstate::goal"]["stateconf.set"][0]["require"] 293 assert {next(iter(i.values())) for i in reqs} == set("ABCDE") 294 295 296def test_implicit_require_with_goal_state(renderer): 297 result = renderer( 298 """ 299{% for sid in "ABCDE": %} 300{{sid}}: 301 cmd.run: 302 - name: echo this is {{sid}} 303 - cwd: / 304{% endfor %} 305 306F: 307 cmd.run: 308 - name: echo this is F 309 - cwd: / 310 - require: 311 - cmd: A 312 - cmd: B 313 314G: 315 cmd.run: 316 - name: echo this is G 317 - cwd: / 318 - require: 319 - cmd: D 320 - cmd: F 321""", 322 sls="test", 323 argline="-o yaml . jinja", 324 ) 325 326 sids = "ABCDEFG"[::-1] 327 for i, sid in enumerate(sids): 328 if i < len(sids) - 1: 329 assert result[sid]["cmd.run"][2]["require"][0]["cmd"] == sids[i + 1] 330 331 F_args = result["F"]["cmd.run"] 332 assert len(F_args) == 3 333 F_req = F_args[2]["require"] 334 assert len(F_req) == 3 335 assert F_req[1]["cmd"] == "A" 336 assert F_req[2]["cmd"] == "B" 337 338 G_args = result["G"]["cmd.run"] 339 assert len(G_args) == 3 340 G_req = G_args[2]["require"] 341 assert len(G_req) == 3 342 assert G_req[1]["cmd"] == "D" 343 assert G_req[2]["cmd"] == "F" 344 345 goal_args = result["test::goal"]["stateconf.set"] 346 assert len(goal_args) == 1 347 assert [next(iter(i.values())) for i in goal_args[0]["require"]] == list("ABCDEFG") 348 349 350def test_slsdir(renderer): 351 result = renderer( 352 """ 353formula/woot.sls: 354 cmd.run: 355 - name: echo {{ slspath }} 356 - cwd: / 357""", 358 sls="formula.woot", 359 argline="yaml . jinja", 360 ) 361 362 r = result["formula/woot.sls"]["cmd.run"][0]["name"] 363 assert r == "echo formula/woot" 364