From 7318523b50c48e3aed3b52ef745dde34f3f2a9ed Mon Sep 17 00:00:00 2001 From: Guilherme Martins Crocetti Date: Sun, 22 Mar 2020 16:30:16 -0300 Subject: [PATCH 1/6] Add cloudformation support for EventBridge --- moto/cloudformation/parsing.py | 3 +++ moto/events/models.py | 18 ++++++++++++++ .../test_cloudformation_stack_crud.py | 24 +++++++++++++++++++ 3 files changed, 45 insertions(+) diff --git a/moto/cloudformation/parsing.py b/moto/cloudformation/parsing.py index 79276c8fc..60eee63aa 100644 --- a/moto/cloudformation/parsing.py +++ b/moto/cloudformation/parsing.py @@ -1,3 +1,4 @@ + from __future__ import unicode_literals import functools import json @@ -18,6 +19,7 @@ from moto.ec2 import models as ec2_models from moto.ecs import models as ecs_models from moto.elb import models as elb_models from moto.elbv2 import models as elbv2_models +from moto.events import models as events_models from moto.iam import models as iam_models from moto.kinesis import models as kinesis_models from moto.kms import models as kms_models @@ -94,6 +96,7 @@ MODEL_MAP = { "AWS::SNS::Topic": sns_models.Topic, "AWS::S3::Bucket": s3_models.FakeBucket, "AWS::SQS::Queue": sqs_models.Queue, + "AWS::Events::Rule": events_models.Rule, } # http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-properties-name.html diff --git a/moto/events/models.py b/moto/events/models.py index a80b86daa..2f6f3b869 100644 --- a/moto/events/models.py +++ b/moto/events/models.py @@ -55,6 +55,24 @@ class Rule(BaseModel): if index is not None: self.targets.pop(index) + @classmethod + def create_from_cloudformation_json( + cls, resource_name, cloudformation_json, region_name + ): + properties = cloudformation_json["Properties"] + event_backend = events_backends[region_name] + event_name = properties.get("Name") or resource_name + return event_backend.put_rule(name=event_name, **properties) + + @classmethod + def delete_from_cloudformation_json( + cls, resource_name, cloudformation_json, region_name + ): + properties = cloudformation_json["Properties"] + event_backend = events_backends[region_name] + event_name = properties.get("Name") or resource_name + event_backend.delete_rule(name=event_name) + class EventBus(BaseModel): def __init__(self, region_name, name): diff --git a/tests/test_cloudformation/test_cloudformation_stack_crud.py b/tests/test_cloudformation/test_cloudformation_stack_crud.py index 3d1b2ab8c..f6d359ec0 100644 --- a/tests/test_cloudformation/test_cloudformation_stack_crud.py +++ b/tests/test_cloudformation/test_cloudformation_stack_crud.py @@ -596,6 +596,30 @@ def test_create_stack_kinesis(): assert len(resources) == 1 +@mock_cloudformation_deprecated +def test_create_stack_events(): + conn = boto.connect_cloudformation() + dummy_template = { + "AWSTemplateFormatVersion": "2010-09-09", + "Description": "Stack Kinesis Test 1", + "Parameters": {}, + "Resources": { + "event": { + "Type": "AWS::Events::Rule", + "Properties": { + "State": "ENABLED", + "ScheduleExpression": "rate(5 minutes)", + }, + } + }, + } + conn.create_stack("test_stack_events_1", template_body=json.dumps(dummy_template)) + stack = conn.describe_stacks()[0] + + resources = stack.list_resources() + resources.should.have.length_of(1) + + def get_role_name(): with mock_iam_deprecated(): iam = boto.connect_iam() From a1f664d2bbbb4788d567afe2f2ae9f42ba924240 Mon Sep 17 00:00:00 2001 From: Guilherme Martins Crocetti Date: Sun, 22 Mar 2020 17:32:37 -0300 Subject: [PATCH 2/6] Change put_rule (and it's response) and fix tests_events/ --- moto/events/models.py | 8 ++++---- moto/events/responses.py | 4 ++-- tests/test_events/test_events.py | 19 ++++++++++++++++--- 3 files changed, 22 insertions(+), 9 deletions(-) diff --git a/moto/events/models.py b/moto/events/models.py index 2f6f3b869..5c7662ba8 100644 --- a/moto/events/models.py +++ b/moto/events/models.py @@ -250,10 +250,10 @@ class EventsBackend(BaseBackend): return return_obj def put_rule(self, name, **kwargs): - rule = Rule(name, self.region_name, **kwargs) - self.rules[rule.name] = rule - self.rules_order.append(rule.name) - return rule.arn + new_rule = Rule(name, self.region_name, **kwargs) + self.rules[new_rule.name] = new_rule + self.rules_order.append(new_rule.name) + return new_rule def put_targets(self, name, targets): rule = self.rules.get(name) diff --git a/moto/events/responses.py b/moto/events/responses.py index c9931aabc..55a664b24 100644 --- a/moto/events/responses.py +++ b/moto/events/responses.py @@ -191,7 +191,7 @@ class EventsHandler(BaseResponse): "ValidationException", "Parameter ScheduleExpression is not valid." ) - rule_arn = self.events_backend.put_rule( + rule = self.events_backend.put_rule( name, ScheduleExpression=sched_exp, EventPattern=event_pattern, @@ -200,7 +200,7 @@ class EventsHandler(BaseResponse): RoleArn=role_arn, ) - return json.dumps({"RuleArn": rule_arn}), self.response_headers + return json.dumps({"RuleArn": rule.arn}), self.response_headers def put_targets(self): rule_name = self._get_param("Rule") diff --git a/tests/test_events/test_events.py b/tests/test_events/test_events.py index 80fadb449..27006ff1b 100644 --- a/tests/test_events/test_events.py +++ b/tests/test_events/test_events.py @@ -1,15 +1,16 @@ -from moto.events.models import EventsBackend -from moto.events import mock_events import json import random import unittest import boto3 +import sure # noqa from botocore.exceptions import ClientError -from moto.core.exceptions import JsonRESTError from nose.tools import assert_raises from moto.core import ACCOUNT_ID +from moto.core.exceptions import JsonRESTError +from moto.events import mock_events +from moto.events.models import EventsBackend RULES = [ {"Name": "test1", "ScheduleExpression": "rate(5 minutes)"}, @@ -75,6 +76,18 @@ def generate_environment(): return client +@mock_events +def test_put_rule(): + client = boto3.client("events", "us-west-2") + + client.list_rules()["Rules"].should.have.length_of(0) + + rule_data = get_random_rule() + client.put_rule(**rule_data) + + client.list_rules()["Rules"].should.have.length_of(1) + + @mock_events def test_list_rules(): client = generate_environment() From 98a17dfc464c3b7a73a73c62e7f2868b9018d7a1 Mon Sep 17 00:00:00 2001 From: Guilherme Martins Crocetti Date: Sun, 22 Mar 2020 18:03:42 -0300 Subject: [PATCH 3/6] Add test for boto3 integration --- .../test_cloudformation_stack_crud.py | 8 ++--- .../test_cloudformation_stack_integration.py | 29 +++++++++++++++++++ 2 files changed, 32 insertions(+), 5 deletions(-) diff --git a/tests/test_cloudformation/test_cloudformation_stack_crud.py b/tests/test_cloudformation/test_cloudformation_stack_crud.py index f6d359ec0..d3a03d2bb 100644 --- a/tests/test_cloudformation/test_cloudformation_stack_crud.py +++ b/tests/test_cloudformation/test_cloudformation_stack_crud.py @@ -597,12 +597,10 @@ def test_create_stack_kinesis(): @mock_cloudformation_deprecated -def test_create_stack_events(): +def test_create_stack_events_rule(): conn = boto.connect_cloudformation() - dummy_template = { + events_template = { "AWSTemplateFormatVersion": "2010-09-09", - "Description": "Stack Kinesis Test 1", - "Parameters": {}, "Resources": { "event": { "Type": "AWS::Events::Rule", @@ -613,7 +611,7 @@ def test_create_stack_events(): } }, } - conn.create_stack("test_stack_events_1", template_body=json.dumps(dummy_template)) + conn.create_stack("test_stack_events_1", template_body=json.dumps(events_template)) stack = conn.describe_stacks()[0] resources = stack.list_resources() diff --git a/tests/test_cloudformation/test_cloudformation_stack_integration.py b/tests/test_cloudformation/test_cloudformation_stack_integration.py index a612156c4..2e84180b3 100644 --- a/tests/test_cloudformation/test_cloudformation_stack_integration.py +++ b/tests/test_cloudformation/test_cloudformation_stack_integration.py @@ -29,6 +29,7 @@ from moto import ( mock_ec2_deprecated, mock_elb, mock_elb_deprecated, + mock_events, mock_iam_deprecated, mock_kms, mock_lambda, @@ -2379,3 +2380,31 @@ def test_create_log_group_using_fntransform(): logs_conn = boto3.client("logs", region_name="us-west-2") log_group = logs_conn.describe_log_groups()["logGroups"][0] log_group["logGroupName"].should.equal("some-log-group") + + +@mock_cloudformation +@mock_events +def test_stack_events_rule_integration(): + events_template = { + "AWSTemplateFormatVersion": "2010-09-09", + "Resources": { + "event": { + "Type": "AWS::Events::Rule", + "Properties": { + "Name": "quick-fox", + "State": "ENABLED", + "ScheduleExpression": "rate(5 minutes)", + }, + } + }, + } + cf_conn = boto3.client("cloudformation", "us-west-2") + cf_conn.create_stack( + StackName="test_stack", TemplateBody=json.dumps(events_template), + ) + + result = boto3.client("events", "us-west-2").list_rules() + result["Rules"].should.have.length_of(1) + result["Rules"][0]["Name"].should.equal("quick-fox") + result["Rules"][0]["State"].should.equal("ENABLED") + result["Rules"][0]["ScheduleExpression"].should.equal("rate(5 minutes)") From 6180cf7a45d294d31d63c514aa22ed78b0cf77e0 Mon Sep 17 00:00:00 2001 From: Guilherme Martins Crocetti Date: Sun, 22 Mar 2020 18:08:12 -0300 Subject: [PATCH 4/6] Fix blank space --- moto/cloudformation/parsing.py | 1 - 1 file changed, 1 deletion(-) diff --git a/moto/cloudformation/parsing.py b/moto/cloudformation/parsing.py index 60eee63aa..cc4daf9ce 100644 --- a/moto/cloudformation/parsing.py +++ b/moto/cloudformation/parsing.py @@ -1,4 +1,3 @@ - from __future__ import unicode_literals import functools import json From c96efe531e3de2ba88028cf44e753cdcbe902b7a Mon Sep 17 00:00:00 2001 From: Guilherme Martins Crocetti Date: Mon, 23 Mar 2020 22:14:34 -0300 Subject: [PATCH 5/6] Add delete method for cloudformation's deletion --- moto/events/models.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/moto/events/models.py b/moto/events/models.py index 5c7662ba8..5f5909907 100644 --- a/moto/events/models.py +++ b/moto/events/models.py @@ -26,12 +26,6 @@ class Rule(BaseModel): self.role_arn = kwargs.get("RoleArn") self.targets = [] - def enable(self): - self.state = "ENABLED" - - def disable(self): - self.state = "DISABLED" - # This song and dance for targets is because we need order for Limits and NextTokens, but can't use OrderedDicts # with Python 2.6, so tracking it with an array it is. def _check_target_exists(self, target_id): @@ -40,6 +34,16 @@ class Rule(BaseModel): return i return None + def enable(self): + self.state = "ENABLED" + + def disable(self): + self.state = "DISABLED" + + def delete(self, region_name): + event_backend = events_backends[region_name] + event_backend.delete_rule(name=self.name) + def put_targets(self, targets): # Not testing for valid ARNs. for target in targets: From 788b8fb6e152a3ae52827009679e059bd3874c75 Mon Sep 17 00:00:00 2001 From: Guilherme Martins Crocetti Date: Mon, 23 Mar 2020 22:17:02 -0300 Subject: [PATCH 6/6] Add tests for Events::Rule integration with cf --- .../test_cloudformation_stack_crud.py | 22 ------ .../test_cloudformation_stack_integration.py | 68 +++++++++++++++++-- 2 files changed, 61 insertions(+), 29 deletions(-) diff --git a/tests/test_cloudformation/test_cloudformation_stack_crud.py b/tests/test_cloudformation/test_cloudformation_stack_crud.py index d3a03d2bb..3d1b2ab8c 100644 --- a/tests/test_cloudformation/test_cloudformation_stack_crud.py +++ b/tests/test_cloudformation/test_cloudformation_stack_crud.py @@ -596,28 +596,6 @@ def test_create_stack_kinesis(): assert len(resources) == 1 -@mock_cloudformation_deprecated -def test_create_stack_events_rule(): - conn = boto.connect_cloudformation() - events_template = { - "AWSTemplateFormatVersion": "2010-09-09", - "Resources": { - "event": { - "Type": "AWS::Events::Rule", - "Properties": { - "State": "ENABLED", - "ScheduleExpression": "rate(5 minutes)", - }, - } - }, - } - conn.create_stack("test_stack_events_1", template_body=json.dumps(events_template)) - stack = conn.describe_stacks()[0] - - resources = stack.list_resources() - resources.should.have.length_of(1) - - def get_role_name(): with mock_iam_deprecated(): iam = boto.connect_iam() diff --git a/tests/test_cloudformation/test_cloudformation_stack_integration.py b/tests/test_cloudformation/test_cloudformation_stack_integration.py index 2e84180b3..e50179660 100644 --- a/tests/test_cloudformation/test_cloudformation_stack_integration.py +++ b/tests/test_cloudformation/test_cloudformation_stack_integration.py @@ -2384,11 +2384,11 @@ def test_create_log_group_using_fntransform(): @mock_cloudformation @mock_events -def test_stack_events_rule_integration(): +def test_stack_events_create_rule_integration(): events_template = { "AWSTemplateFormatVersion": "2010-09-09", "Resources": { - "event": { + "Event": { "Type": "AWS::Events::Rule", "Properties": { "Name": "quick-fox", @@ -2403,8 +2403,62 @@ def test_stack_events_rule_integration(): StackName="test_stack", TemplateBody=json.dumps(events_template), ) - result = boto3.client("events", "us-west-2").list_rules() - result["Rules"].should.have.length_of(1) - result["Rules"][0]["Name"].should.equal("quick-fox") - result["Rules"][0]["State"].should.equal("ENABLED") - result["Rules"][0]["ScheduleExpression"].should.equal("rate(5 minutes)") + rules = boto3.client("events", "us-west-2").list_rules() + rules["Rules"].should.have.length_of(1) + rules["Rules"][0]["Name"].should.equal("quick-fox") + rules["Rules"][0]["State"].should.equal("ENABLED") + rules["Rules"][0]["ScheduleExpression"].should.equal("rate(5 minutes)") + + +@mock_cloudformation +@mock_events +def test_stack_events_delete_rule_integration(): + events_template = { + "AWSTemplateFormatVersion": "2010-09-09", + "Resources": { + "Event": { + "Type": "AWS::Events::Rule", + "Properties": { + "Name": "quick-fox", + "State": "ENABLED", + "ScheduleExpression": "rate(5 minutes)", + }, + } + }, + } + cf_conn = boto3.client("cloudformation", "us-west-2") + cf_conn.create_stack( + StackName="test_stack", TemplateBody=json.dumps(events_template), + ) + + rules = boto3.client("events", "us-west-2").list_rules() + rules["Rules"].should.have.length_of(1) + + cf_conn.delete_stack(StackName="test_stack") + + rules = boto3.client("events", "us-west-2").list_rules() + rules["Rules"].should.have.length_of(0) + + +@mock_cloudformation +@mock_events +def test_stack_events_create_rule_without_name_integration(): + events_template = { + "AWSTemplateFormatVersion": "2010-09-09", + "Resources": { + "Event": { + "Type": "AWS::Events::Rule", + "Properties": { + "State": "ENABLED", + "ScheduleExpression": "rate(5 minutes)", + }, + } + }, + } + cf_conn = boto3.client("cloudformation", "us-west-2") + cf_conn.create_stack( + StackName="test_stack", TemplateBody=json.dumps(events_template), + ) + + rules = boto3.client("events", "us-west-2").list_rules() + rules["Rules"][0]["Name"].should.contain("test_stack-Event-")