Repository: getmoto/moto

Issue:
CloudFormation support for AWS::Events::EventBus
## Feature Request

Hi, I'm trying to mock the creation of an AWS EventBus using Serverless framework and local stack, however it seems there is no Moto CloudFormation support for AWS::Events::EventBus resources.

### Serverless.yml:
```
resources:
  Resources:
    EventBus:
        Type: AWS::Events::EventBus
        Properties: 
          Name: New-Event-Bus
```

LocalStack uses the package: moto-ext>=1.3.15.12.

 ## Expected Results:

On deployment a new EventBus called "New-Event-Bus" is created.


Discussion:

Resolving patch:
diff --git a/moto/cloudformation/parsing.py b/moto/cloudformation/parsing.py
--- a/moto/cloudformation/parsing.py
+++ b/moto/cloudformation/parsing.py
@@ -96,6 +96,7 @@
     "AWS::S3::Bucket": s3_models.FakeBucket,
     "AWS::SQS::Queue": sqs_models.Queue,
     "AWS::Events::Rule": events_models.Rule,
+    "AWS::Events::EventBus": events_models.EventBus,
 }
 
 UNDOCUMENTED_NAME_TYPE_MAP = {
diff --git a/moto/events/models.py b/moto/events/models.py
--- a/moto/events/models.py
+++ b/moto/events/models.py
@@ -134,6 +134,52 @@ def policy(self):
 
         return json.dumps(policy)
 
+    def delete(self, region_name):
+        event_backend = events_backends[region_name]
+        event_backend.delete_event_bus(name=self.name)
+
+    def get_cfn_attribute(self, attribute_name):
+        from moto.cloudformation.exceptions import UnformattedGetAttTemplateException
+
+        if attribute_name == "Arn":
+            return self.arn
+        elif attribute_name == "Name":
+            return self.name
+        elif attribute_name == "Policy":
+            return self.policy
+
+        raise UnformattedGetAttTemplateException()
+
+    @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["Name"]
+        event_source_name = properties.get("EventSourceName")
+        return event_backend.create_event_bus(
+            name=event_name, event_source_name=event_source_name
+        )
+
+    @classmethod
+    def update_from_cloudformation_json(
+        cls, original_resource, new_resource_name, cloudformation_json, region_name
+    ):
+        original_resource.delete(region_name)
+        return cls.create_from_cloudformation_json(
+            new_resource_name, cloudformation_json, region_name
+        )
+
+    @classmethod
+    def delete_from_cloudformation_json(
+        cls, resource_name, cloudformation_json, region_name
+    ):
+        properties = cloudformation_json["Properties"]
+        event_backend = events_backends[region_name]
+        event_bus_name = properties["Name"]
+        event_backend.delete_event_bus(event_bus_name)
+
 
 class EventsBackend(BaseBackend):
     ACCOUNT_ID = re.compile(r"^(\d{1,12}|\*)$")
@@ -369,7 +415,7 @@ def describe_event_bus(self, name):
 
         return event_bus
 
-    def create_event_bus(self, name, event_source_name):
+    def create_event_bus(self, name, event_source_name=None):
         if name in self.event_buses:
             raise JsonRESTError(
                 "ResourceAlreadyExistsException",
@@ -406,7 +452,6 @@ def delete_event_bus(self, name):
             raise JsonRESTError(
                 "ValidationException", "Cannot delete event bus default."
             )
-
         self.event_buses.pop(name, None)
 
     def list_tags_for_resource(self, arn):


Test patch:
diff --git a/tests/test_cloudformation/test_cloudformation_stack_integration.py b/tests/test_cloudformation/test_cloudformation_stack_integration.py
--- a/tests/test_cloudformation/test_cloudformation_stack_integration.py
+++ b/tests/test_cloudformation/test_cloudformation_stack_integration.py
@@ -2585,3 +2585,178 @@ def test_autoscaling_propagate_tags():
 
     assert propagation_dict["test-key-propagate"]
     assert not propagation_dict["test-key-no-propagate"]
+
+
+@mock_cloudformation
+@mock_events
+def test_stack_eventbus_create_from_cfn_integration():
+    eventbus_template = """{
+        "AWSTemplateFormatVersion": "2010-09-09",
+        "Resources": {
+            "EventBus": {
+                "Type": "AWS::Events::EventBus",
+                "Properties": {
+                    "Name": "MyCustomEventBus"
+                },
+            }
+        },
+    }"""
+
+    cf_conn = boto3.client("cloudformation", "us-west-2")
+    cf_conn.create_stack(StackName="test_stack", TemplateBody=eventbus_template)
+
+    event_buses = boto3.client("events", "us-west-2").list_event_buses(
+        NamePrefix="MyCustom"
+    )
+
+    event_buses["EventBuses"].should.have.length_of(1)
+    event_buses["EventBuses"][0]["Name"].should.equal("MyCustomEventBus")
+
+
+@mock_cloudformation
+@mock_events
+def test_stack_events_delete_eventbus_integration():
+    eventbus_template = """{
+        "AWSTemplateFormatVersion": "2010-09-09",
+        "Resources": {
+            "EventBus": {
+                "Type": "AWS::Events::EventBus",
+                "Properties": {
+                    "Name": "MyCustomEventBus"
+                },
+            }
+        },
+    }"""
+    cf_conn = boto3.client("cloudformation", "us-west-2")
+    cf_conn.create_stack(StackName="test_stack", TemplateBody=eventbus_template)
+
+    event_buses = boto3.client("events", "us-west-2").list_event_buses(
+        NamePrefix="MyCustom"
+    )
+    event_buses["EventBuses"].should.have.length_of(1)
+
+    cf_conn.delete_stack(StackName="test_stack")
+
+    event_buses = boto3.client("events", "us-west-2").list_event_buses(
+        NamePrefix="MyCustom"
+    )
+    event_buses["EventBuses"].should.have.length_of(0)
+
+
+@mock_cloudformation
+@mock_events
+def test_stack_events_delete_from_cfn_integration():
+    eventbus_template = Template(
+        """{
+        "AWSTemplateFormatVersion": "2010-09-09",
+        "Resources": {
+            "$resource_name": {
+                "Type": "AWS::Events::EventBus",
+                "Properties": {
+                    "Name": "$name"
+                },
+            }
+        },
+    }"""
+    )
+
+    cf_conn = boto3.client("cloudformation", "us-west-2")
+
+    original_template = eventbus_template.substitute(
+        {"resource_name": "original", "name": "MyCustomEventBus"}
+    )
+    cf_conn.create_stack(StackName="test_stack", TemplateBody=original_template)
+
+    original_event_buses = boto3.client("events", "us-west-2").list_event_buses(
+        NamePrefix="MyCustom"
+    )
+    original_event_buses["EventBuses"].should.have.length_of(1)
+
+    original_eventbus = original_event_buses["EventBuses"][0]
+
+    updated_template = eventbus_template.substitute(
+        {"resource_name": "updated", "name": "AnotherEventBus"}
+    )
+    cf_conn.update_stack(StackName="test_stack", TemplateBody=updated_template)
+
+    update_event_buses = boto3.client("events", "us-west-2").list_event_buses(
+        NamePrefix="AnotherEventBus"
+    )
+    update_event_buses["EventBuses"].should.have.length_of(1)
+    update_event_buses["EventBuses"][0]["Arn"].shouldnt.equal(original_eventbus["Arn"])
+
+
+@mock_cloudformation
+@mock_events
+def test_stack_events_update_from_cfn_integration():
+    eventbus_template = Template(
+        """{
+        "AWSTemplateFormatVersion": "2010-09-09",
+        "Resources": {
+            "EventBus": {
+                "Type": "AWS::Events::EventBus",
+                "Properties": {
+                    "Name": "$name"
+                },
+            }
+        },
+    }"""
+    )
+
+    cf_conn = boto3.client("cloudformation", "us-west-2")
+
+    original_template = eventbus_template.substitute({"name": "MyCustomEventBus"})
+    cf_conn.create_stack(StackName="test_stack", TemplateBody=original_template)
+
+    original_event_buses = boto3.client("events", "us-west-2").list_event_buses(
+        NamePrefix="MyCustom"
+    )
+    original_event_buses["EventBuses"].should.have.length_of(1)
+
+    original_eventbus = original_event_buses["EventBuses"][0]
+
+    updated_template = eventbus_template.substitute({"name": "NewEventBus"})
+    cf_conn.update_stack(StackName="test_stack", TemplateBody=updated_template)
+
+    update_event_buses = boto3.client("events", "us-west-2").list_event_buses(
+        NamePrefix="NewEventBus"
+    )
+    update_event_buses["EventBuses"].should.have.length_of(1)
+    update_event_buses["EventBuses"][0]["Name"].should.equal("NewEventBus")
+    update_event_buses["EventBuses"][0]["Arn"].shouldnt.equal(original_eventbus["Arn"])
+
+
+@mock_cloudformation
+@mock_events
+def test_stack_events_get_attribute_integration():
+    eventbus_template = """{
+        "AWSTemplateFormatVersion": "2010-09-09",
+        "Resources": {
+            "EventBus": {
+                "Type": "AWS::Events::EventBus",
+                "Properties": {
+                    "Name": "MyEventBus"
+                },
+            }
+        },
+        "Outputs": {
+            "bus_arn": {"Value": {"Fn::GetAtt": ["EventBus", "Arn"]}},
+            "bus_name": {"Value": {"Fn::GetAtt": ["EventBus", "Name"]}},
+        }
+    }"""
+
+    cf = boto3.client("cloudformation", "us-west-2")
+    events = boto3.client("events", "us-west-2")
+
+    cf.create_stack(StackName="test_stack", TemplateBody=eventbus_template)
+
+    stack = cf.describe_stacks(StackName="test_stack")["Stacks"][0]
+    outputs = stack["Outputs"]
+
+    output_arn = list(filter(lambda item: item["OutputKey"] == "bus_arn", outputs))[0]
+    output_name = list(filter(lambda item: item["OutputKey"] == "bus_name", outputs))[0]
+
+    event_bus = events.list_event_buses(NamePrefix="MyEventBus")["EventBuses"][0]
+
+    output_arn["OutputValue"].should.equal(event_bus["Arn"])
+    output_name["OutputValue"].should.equal(event_bus["Name"])