1import logging
2import random
3import string
4
5import pytest
6import salt.config
7import salt.loader
8import salt.states.boto_cloudwatch_event as boto_cloudwatch_event
9from tests.support.mock import MagicMock, patch
10
11boto = pytest.importorskip("boto")
12boto3 = pytest.importorskip("boto3", "1.2.1")
13botocore = pytest.importorskip("botocore", "1.4.41")
14
15log = logging.getLogger(__name__)
16
17
18class GlobalConfig:
19    region = "us-east-1"
20    access_key = "GKTADJGHEIQSXMKKRBJ08H"
21    secret_key = "askdjghsdfjkghWupUjasdflkdfklgjsdfjajkghs"
22    conn_parameters = {
23        "region": region,
24        "key": access_key,
25        "keyid": secret_key,
26        "profile": {},
27    }
28    error_message = (
29        "An error occurred (101) when calling the {0} operation: Test-defined error"
30    )
31    error_content = {"Error": {"Code": 101, "Message": "Test-defined error"}}
32    rule_name = "test_thing_type"
33    rule_desc = "test_thing_type_desc"
34    rule_sched = "rate(20 min)"
35    rule_arn = "arn:::::rule/arn"
36    rule_ret = dict(
37        Arn=rule_arn,
38        Description=rule_desc,
39        EventPattern=None,
40        Name=rule_name,
41        RoleArn=None,
42        ScheduleExpression=rule_sched,
43        State="ENABLED",
44    )
45
46
47@pytest.fixture
48def global_config():
49    params = GlobalConfig()
50    return params
51
52
53@pytest.fixture
54def configure_loader_modules():
55    opts = salt.config.DEFAULT_MINION_OPTS.copy()
56    opts["grains"] = salt.loader.grains(opts)
57    ctx = {}
58    utils = salt.loader.utils(
59        opts,
60        whitelist=["boto3", "args", "systemd", "path", "platform"],
61        context=ctx,
62    )
63    serializers = salt.loader.serializers(opts)
64    funcs = funcs = salt.loader.minion_mods(
65        opts, context=ctx, utils=utils, whitelist=["boto_cloudwatch_event"]
66    )
67    salt_states = salt.loader.states(
68        opts=opts,
69        functions=funcs,
70        utils=utils,
71        whitelist=["boto_cloudwatch_event"],
72        serializers=serializers,
73    )
74    return {
75        boto_cloudwatch_event: {
76            "__opts__": opts,
77            "__salt__": funcs,
78            "__utils__": utils,
79            "__states__": salt_states,
80            "__serializers__": serializers,
81        }
82    }
83
84
85def test_present_when_failing_to_describe_rule(global_config):
86    """
87    Tests exceptions when checking rule existence
88    """
89    global_config.conn_parameters["key"] = "".join(
90        random.choice(string.ascii_lowercase + string.digits) for _ in range(50)
91    )
92    patcher = patch("boto3.session.Session")
93    mock_session = patcher.start()
94    session_instance = mock_session.return_value
95    conn = MagicMock()
96    session_instance.client.return_value = conn
97    conn.list_rules.side_effect = botocore.exceptions.ClientError(
98        global_config.error_content, "error on list rules"
99    )
100    result = boto_cloudwatch_event.__states__["boto_cloudwatch_event.present"](
101        name="test present",
102        Name=global_config.rule_name,
103        Description=global_config.rule_desc,
104        ScheduleExpression=global_config.rule_sched,
105        Targets=[{"Id": "target1", "Arn": "arn::::::*"}],
106        **global_config.conn_parameters
107    )
108    assert result.get("result") is False
109    assert "error on list rules" in result.get("comment", {})
110
111
112def test_present_when_failing_to_create_a_new_rule(global_config):
113    """
114    Tests present on a rule name that doesn't exist and
115    an error is thrown on creation.
116    """
117    global_config.conn_parameters["key"] = "".join(
118        random.choice(string.ascii_lowercase + string.digits) for _ in range(50)
119    )
120    patcher = patch("boto3.session.Session")
121    mock_session = patcher.start()
122    session_instance = mock_session.return_value
123    conn = MagicMock()
124    session_instance.client.return_value = conn
125    conn.list_rules.return_value = {"Rules": []}
126    conn.put_rule.side_effect = botocore.exceptions.ClientError(
127        global_config.error_content, "put_rule"
128    )
129    result = boto_cloudwatch_event.__states__["boto_cloudwatch_event.present"](
130        name="test present",
131        Name=global_config.rule_name,
132        Description=global_config.rule_desc,
133        ScheduleExpression=global_config.rule_sched,
134        Targets=[{"Id": "target1", "Arn": "arn::::::*"}],
135        **global_config.conn_parameters
136    )
137    assert result.get("result") is False
138    assert "put_rule" in result.get("comment", "")
139
140
141def test_present_when_failing_to_describe_the_new_rule(global_config):
142    """
143    Tests present on a rule name that doesn't exist and
144    an error is thrown when adding targets.
145    """
146    global_config.conn_parameters["key"] = "".join(
147        random.choice(string.ascii_lowercase + string.digits) for _ in range(50)
148    )
149    patcher = patch("boto3.session.Session")
150    mock_session = patcher.start()
151    session_instance = mock_session.return_value
152    conn = MagicMock()
153    session_instance.client.return_value = conn
154    conn.list_rules.return_value = {"Rules": []}
155    conn.put_rule.return_value = global_config.rule_ret
156    conn.describe_rule.side_effect = botocore.exceptions.ClientError(
157        global_config.error_content, "describe_rule"
158    )
159    result = boto_cloudwatch_event.__states__["boto_cloudwatch_event.present"](
160        name="test present",
161        Name=global_config.rule_name,
162        Description=global_config.rule_desc,
163        ScheduleExpression=global_config.rule_sched,
164        Targets=[{"Id": "target1", "Arn": "arn::::::*"}],
165        **global_config.conn_parameters
166    )
167    assert result.get("result") is False
168    assert "describe_rule" in result.get("comment", "")
169
170
171def test_present_when_failing_to_create_a_new_rules_targets(global_config):
172    """
173    Tests present on a rule name that doesn't exist and
174    an error is thrown when adding targets.
175    """
176    global_config.conn_parameters["key"] = "".join(
177        random.choice(string.ascii_lowercase + string.digits) for _ in range(50)
178    )
179    patcher = patch("boto3.session.Session")
180    mock_session = patcher.start()
181    session_instance = mock_session.return_value
182    conn = MagicMock()
183    session_instance.client.return_value = conn
184    conn.list_rules.return_value = {"Rules": []}
185    conn.put_rule.return_value = global_config.rule_ret
186    conn.describe_rule.return_value = global_config.rule_ret
187    conn.put_targets.side_effect = botocore.exceptions.ClientError(
188        global_config.error_content, "put_targets"
189    )
190    result = boto_cloudwatch_event.__states__["boto_cloudwatch_event.present"](
191        name="test present",
192        Name=global_config.rule_name,
193        Description=global_config.rule_desc,
194        ScheduleExpression=global_config.rule_sched,
195        Targets=[{"Id": "target1", "Arn": "arn::::::*"}],
196        **global_config.conn_parameters
197    )
198    assert result.get("result") is False
199    assert "put_targets" in result.get("comment", "")
200
201
202def test_present_when_rule_does_not_exist(global_config):
203    """
204    Tests the successful case of creating a new rule, and updating its
205    targets
206    """
207    global_config.conn_parameters["key"] = "".join(
208        random.choice(string.ascii_lowercase + string.digits) for _ in range(50)
209    )
210    patcher = patch("boto3.session.Session")
211    mock_session = patcher.start()
212    session_instance = mock_session.return_value
213    conn = MagicMock()
214    session_instance.client.return_value = conn
215    conn.list_rules.return_value = {"Rules": []}
216    conn.put_rule.return_value = global_config.rule_ret
217    conn.describe_rule.return_value = global_config.rule_ret
218    conn.put_targets.return_value = {"FailedEntryCount": 0}
219    result = boto_cloudwatch_event.__states__["boto_cloudwatch_event.present"](
220        name="test present",
221        Name=global_config.rule_name,
222        Description=global_config.rule_desc,
223        ScheduleExpression=global_config.rule_sched,
224        Targets=[{"Id": "target1", "Arn": "arn::::::*"}],
225        **global_config.conn_parameters
226    )
227    assert result.get("result") is True
228
229
230def test_present_when_failing_to_update_an_existing_rule(global_config):
231    """
232    Tests present on an existing rule where an error is thrown on updating the pool properties.
233    """
234    global_config.conn_parameters["key"] = "".join(
235        random.choice(string.ascii_lowercase + string.digits) for _ in range(50)
236    )
237    patcher = patch("boto3.session.Session")
238    mock_session = patcher.start()
239    session_instance = mock_session.return_value
240    conn = MagicMock()
241    session_instance.client.return_value = conn
242    conn.list_rules.return_value = {"Rules": [global_config.rule_ret]}
243    conn.describe_rule.side_effect = botocore.exceptions.ClientError(
244        global_config.error_content, "describe_rule"
245    )
246    result = boto_cloudwatch_event.__states__["boto_cloudwatch_event.present"](
247        name="test present",
248        Name=global_config.rule_name,
249        Description=global_config.rule_desc,
250        ScheduleExpression=global_config.rule_sched,
251        Targets=[{"Id": "target1", "Arn": "arn::::::*"}],
252        **global_config.conn_parameters
253    )
254    assert result.get("result") is False
255    assert "describe_rule" in result.get("comment", "")
256
257
258def test_present_when_failing_to_get_targets(global_config):
259    """
260    Tests present on an existing rule where put_rule succeeded, but an error
261    is thrown on getting targets
262    """
263    global_config.conn_parameters["key"] = "".join(
264        random.choice(string.ascii_lowercase + string.digits) for _ in range(50)
265    )
266    patcher = patch("boto3.session.Session")
267    mock_session = patcher.start()
268    session_instance = mock_session.return_value
269    conn = MagicMock()
270    session_instance.client.return_value = conn
271    conn.list_rules.return_value = {"Rules": [global_config.rule_ret]}
272    conn.put_rule.return_value = global_config.rule_ret
273    conn.describe_rule.return_value = global_config.rule_ret
274    conn.list_targets_by_rule.side_effect = botocore.exceptions.ClientError(
275        global_config.error_content, "list_targets"
276    )
277    result = boto_cloudwatch_event.__states__["boto_cloudwatch_event.present"](
278        name="test present",
279        Name=global_config.rule_name,
280        Description=global_config.rule_desc,
281        ScheduleExpression=global_config.rule_sched,
282        Targets=[{"Id": "target1", "Arn": "arn::::::*"}],
283        **global_config.conn_parameters
284    )
285    assert result.get("result") is False
286    assert "list_targets" in result.get("comment", "")
287
288
289def test_present_when_failing_to_put_targets(global_config):
290    """
291    Tests present on an existing rule where put_rule succeeded, but an error
292    is thrown on putting targets
293    """
294    global_config.conn_parameters["key"] = "".join(
295        random.choice(string.ascii_lowercase + string.digits) for _ in range(50)
296    )
297    patcher = patch("boto3.session.Session")
298    mock_session = patcher.start()
299    session_instance = mock_session.return_value
300    conn = MagicMock()
301    session_instance.client.return_value = conn
302    conn.list_rules.return_value = {"Rules": []}
303    conn.put_rule.return_value = global_config.rule_ret
304    conn.describe_rule.return_value = global_config.rule_ret
305    conn.list_targets.return_value = {"Targets": []}
306    conn.put_targets.side_effect = botocore.exceptions.ClientError(
307        global_config.error_content, "put_targets"
308    )
309    result = boto_cloudwatch_event.__states__["boto_cloudwatch_event.present"](
310        name="test present",
311        Name=global_config.rule_name,
312        Description=global_config.rule_desc,
313        ScheduleExpression=global_config.rule_sched,
314        Targets=[{"Id": "target1", "Arn": "arn::::::*"}],
315        **global_config.conn_parameters
316    )
317    assert result.get("result") is False
318    assert "put_targets" in result.get("comment", "")
319
320
321def test_present_when_putting_targets(global_config):
322    """
323    Tests present on an existing rule where put_rule succeeded, and targets
324    must be added
325    """
326    global_config.conn_parameters["key"] = "".join(
327        random.choice(string.ascii_lowercase + string.digits) for _ in range(50)
328    )
329    patcher = patch("boto3.session.Session")
330    mock_session = patcher.start()
331    session_instance = mock_session.return_value
332    conn = MagicMock()
333    session_instance.client.return_value = conn
334    conn.list_rules.return_value = {"Rules": []}
335    conn.put_rule.return_value = global_config.rule_ret
336    conn.describe_rule.return_value = global_config.rule_ret
337    conn.list_targets.return_value = {"Targets": []}
338    conn.put_targets.return_value = {"FailedEntryCount": 0}
339    result = boto_cloudwatch_event.__states__["boto_cloudwatch_event.present"](
340        name="test present",
341        Name=global_config.rule_name,
342        Description=global_config.rule_desc,
343        ScheduleExpression=global_config.rule_sched,
344        Targets=[{"Id": "target1", "Arn": "arn::::::*"}],
345        **global_config.conn_parameters
346    )
347    assert result.get("result") is True
348
349
350def test_present_when_removing_targets(global_config):
351    """
352    Tests present on an existing rule where put_rule succeeded, and targets
353    must be removed
354    """
355    global_config.conn_parameters["key"] = "".join(
356        random.choice(string.ascii_lowercase + string.digits) for _ in range(50)
357    )
358    patcher = patch("boto3.session.Session")
359    mock_session = patcher.start()
360    session_instance = mock_session.return_value
361    conn = MagicMock()
362    session_instance.client.return_value = conn
363    conn.list_rules.return_value = {"Rules": []}
364    conn.put_rule.return_value = global_config.rule_ret
365    conn.describe_rule.return_value = global_config.rule_ret
366    conn.list_targets.return_value = {"Targets": [{"Id": "target1"}, {"Id": "target2"}]}
367    conn.put_targets.return_value = {"FailedEntryCount": 0}
368    result = boto_cloudwatch_event.__states__["boto_cloudwatch_event.present"](
369        name="test present",
370        Name=global_config.rule_name,
371        Description=global_config.rule_desc,
372        ScheduleExpression=global_config.rule_sched,
373        Targets=[{"Id": "target1", "Arn": "arn::::::*"}],
374        **global_config.conn_parameters
375    )
376    assert result.get("result") is True
377
378
379def test_absent_when_failing_to_describe_rule(global_config):
380    """
381    Tests exceptions when checking rule existence
382    """
383    global_config.conn_parameters["key"] = "".join(
384        random.choice(string.ascii_lowercase + string.digits) for _ in range(50)
385    )
386    patcher = patch("boto3.session.Session")
387    mock_session = patcher.start()
388    session_instance = mock_session.return_value
389    conn = MagicMock()
390    session_instance.client.return_value = conn
391    conn.list_rules.side_effect = botocore.exceptions.ClientError(
392        global_config.error_content, "error on list rules"
393    )
394    result = boto_cloudwatch_event.__states__["boto_cloudwatch_event.absent"](
395        name="test present",
396        Name=global_config.rule_name,
397        **global_config.conn_parameters
398    )
399    assert result.get("result") is False
400    assert "error on list rules" in result.get("comment", {})
401
402
403def test_absent_when_rule_does_not_exist(global_config):
404    """
405    Tests absent on an non-existing rule
406    """
407    global_config.conn_parameters["key"] = "".join(
408        random.choice(string.ascii_lowercase + string.digits) for _ in range(50)
409    )
410    patcher = patch("boto3.session.Session")
411    mock_session = patcher.start()
412    session_instance = mock_session.return_value
413    conn = MagicMock()
414    session_instance.client.return_value = conn
415    conn.list_rules.return_value = {"Rules": []}
416    result = boto_cloudwatch_event.__states__["boto_cloudwatch_event.absent"](
417        name="test absent",
418        Name=global_config.rule_name,
419        **global_config.conn_parameters
420    )
421    assert result.get("result") is True
422    assert result["changes"] == {}
423
424
425def test_absent_when_failing_to_list_targets(global_config):
426    """
427    Tests absent on an rule when the list_targets call fails
428    """
429    global_config.conn_parameters["key"] = "".join(
430        random.choice(string.ascii_lowercase + string.digits) for _ in range(50)
431    )
432    patcher = patch("boto3.session.Session")
433    mock_session = patcher.start()
434    session_instance = mock_session.return_value
435    conn = MagicMock()
436    session_instance.client.return_value = conn
437    conn.list_rules.return_value = {"Rules": [global_config.rule_ret]}
438    conn.list_targets_by_rule.side_effect = botocore.exceptions.ClientError(
439        global_config.error_content, "list_targets"
440    )
441    result = boto_cloudwatch_event.__states__["boto_cloudwatch_event.absent"](
442        name="test absent",
443        Name=global_config.rule_name,
444        **global_config.conn_parameters
445    )
446    assert result.get("result") is False
447    assert "list_targets" in result.get("comment", "")
448
449
450def test_absent_when_failing_to_remove_targets_exception(global_config):
451    """
452    Tests absent on an rule when the remove_targets call fails
453    """
454    global_config.conn_parameters["key"] = "".join(
455        random.choice(string.ascii_lowercase + string.digits) for _ in range(50)
456    )
457    patcher = patch("boto3.session.Session")
458    mock_session = patcher.start()
459    session_instance = mock_session.return_value
460    conn = MagicMock()
461    session_instance.client.return_value = conn
462    conn.list_rules.return_value = {"Rules": [global_config.rule_ret]}
463    conn.list_targets_by_rule.return_value = {"Targets": [{"Id": "target1"}]}
464    conn.remove_targets.side_effect = botocore.exceptions.ClientError(
465        global_config.error_content, "remove_targets"
466    )
467    result = boto_cloudwatch_event.__states__["boto_cloudwatch_event.absent"](
468        name="test absent",
469        Name=global_config.rule_name,
470        **global_config.conn_parameters
471    )
472    assert result.get("result") is False
473    assert "remove_targets" in result.get("comment", "")
474
475
476def test_absent_when_failing_to_remove_targets_nonexception(global_config):
477    """
478    Tests absent on an rule when the remove_targets call fails
479    """
480    global_config.conn_parameters["key"] = "".join(
481        random.choice(string.ascii_lowercase + string.digits) for _ in range(50)
482    )
483    patcher = patch("boto3.session.Session")
484    mock_session = patcher.start()
485    session_instance = mock_session.return_value
486    conn = MagicMock()
487    session_instance.client.return_value = conn
488    conn.list_rules.return_value = {"Rules": [global_config.rule_ret]}
489    conn.list_targets_by_rule.return_value = {"Targets": [{"Id": "target1"}]}
490    conn.remove_targets.return_value = {"FailedEntryCount": 1}
491    result = boto_cloudwatch_event.__states__["boto_cloudwatch_event.absent"](
492        name="test absent",
493        Name=global_config.rule_name,
494        **global_config.conn_parameters
495    )
496    assert result.get("result") is False
497
498
499def test_absent_when_failing_to_delete_rule(global_config):
500    """
501    Tests absent on an rule when the delete_rule call fails
502    """
503    global_config.conn_parameters["key"] = "".join(
504        random.choice(string.ascii_lowercase + string.digits) for _ in range(50)
505    )
506    patcher = patch("boto3.session.Session")
507    mock_session = patcher.start()
508    session_instance = mock_session.return_value
509    conn = MagicMock()
510    session_instance.client.return_value = conn
511    conn.list_rules.return_value = {"Rules": [global_config.rule_ret]}
512    conn.list_targets_by_rule.return_value = {"Targets": [{"Id": "target1"}]}
513    conn.remove_targets.return_value = {"FailedEntryCount": 0}
514    conn.delete_rule.side_effect = botocore.exceptions.ClientError(
515        global_config.error_content, "delete_rule"
516    )
517    result = boto_cloudwatch_event.__states__["boto_cloudwatch_event.absent"](
518        name="test absent",
519        Name=global_config.rule_name,
520        **global_config.conn_parameters
521    )
522    assert result.get("result") is False
523    assert "delete_rule" in result.get("comment", "")
524
525
526def test_absent(global_config):
527    """
528    Tests absent on an rule
529    """
530    global_config.conn_parameters["key"] = "".join(
531        random.choice(string.ascii_lowercase + string.digits) for _ in range(50)
532    )
533    patcher = patch("boto3.session.Session")
534    mock_session = patcher.start()
535    session_instance = mock_session.return_value
536    conn = MagicMock()
537    session_instance.client.return_value = conn
538    conn.list_rules.return_value = {"Rules": [global_config.rule_ret]}
539    conn.list_targets_by_rule.return_value = {"Targets": [{"Id": "target1"}]}
540    conn.remove_targets.return_value = {"FailedEntryCount": 0}
541    result = boto_cloudwatch_event.__states__["boto_cloudwatch_event.absent"](
542        name="test absent",
543        Name=global_config.rule_name,
544        **global_config.conn_parameters
545    )
546    assert result.get("result") is True
547