Merge pull request #2536 from edekadigital/add-events-event-bus

Add events event bus
This commit is contained in:
Steve Pulec 2019-11-15 16:38:51 -06:00 committed by GitHub
commit dddb9dd4d2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 403 additions and 35 deletions

View File

@ -2718,12 +2718,12 @@
- [ ] upgrade_elasticsearch_domain
## events
48% implemented
58% implemented
- [ ] activate_event_source
- [ ] create_event_bus
- [X] create_event_bus
- [ ] create_partner_event_source
- [ ] deactivate_event_source
- [ ] delete_event_bus
- [X] delete_event_bus
- [ ] delete_partner_event_source
- [X] delete_rule
- [X] describe_event_bus
@ -2732,7 +2732,7 @@
- [X] describe_rule
- [X] disable_rule
- [X] enable_rule
- [ ] list_event_buses
- [X] list_event_buses
- [ ] list_event_sources
- [ ] list_partner_event_source_accounts
- [ ] list_partner_event_sources

View File

@ -5,6 +5,7 @@ import boto3
from moto.core.exceptions import JsonRESTError
from moto.core import BaseBackend, BaseModel
from moto.sts.models import ACCOUNT_ID
class Rule(BaseModel):
@ -54,6 +55,42 @@ class Rule(BaseModel):
self.targets.pop(index)
class EventBus(BaseModel):
def __init__(self, region_name, name):
self.region = region_name
self.name = name
self._permissions = {}
@property
def arn(self):
return "arn:aws:events:{region}:{account_id}:event-bus/{name}".format(
region=self.region, account_id=ACCOUNT_ID, name=self.name
)
@property
def policy(self):
if not len(self._permissions):
return None
policy = {"Version": "2012-10-17", "Statement": []}
for sid, permission in self._permissions.items():
policy["Statement"].append(
{
"Sid": sid,
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::{}:root".format(permission["Principal"])
},
"Action": permission["Action"],
"Resource": self.arn,
}
)
return json.dumps(policy)
class EventsBackend(BaseBackend):
ACCOUNT_ID = re.compile(r"^(\d{1,12}|\*)$")
STATEMENT_ID = re.compile(r"^[a-zA-Z0-9-_]{1,64}$")
@ -65,13 +102,19 @@ class EventsBackend(BaseBackend):
self.rules_order = []
self.next_tokens = {}
self.region_name = region_name
self.permissions = {}
self.event_buses = {}
self.event_sources = {}
self._add_default_event_bus()
def reset(self):
region_name = self.region_name
self.__dict__ = {}
self.__init__(region_name)
def _add_default_event_bus(self):
self.event_buses["default"] = EventBus(self.region_name, "default")
def _get_rule_by_index(self, i):
return self.rules.get(self.rules_order[i])
@ -221,9 +264,17 @@ class EventsBackend(BaseBackend):
def test_event_pattern(self):
raise NotImplementedError()
def put_permission(self, action, principal, statement_id):
def put_permission(self, event_bus_name, action, principal, statement_id):
if not event_bus_name:
event_bus_name = "default"
event_bus = self.describe_event_bus(event_bus_name)
if action is None or action != "events:PutEvents":
raise JsonRESTError("InvalidParameterValue", "Action must be PutEvents")
raise JsonRESTError(
"ValidationException",
"Provided value in parameter 'action' is not supported.",
)
if principal is None or self.ACCOUNT_ID.match(principal) is None:
raise JsonRESTError(
@ -235,34 +286,81 @@ class EventsBackend(BaseBackend):
"InvalidParameterValue", "StatementId must match ^[a-zA-Z0-9-_]{1,64}$"
)
self.permissions[statement_id] = {"action": action, "principal": principal}
event_bus._permissions[statement_id] = {
"Action": action,
"Principal": principal,
}
def remove_permission(self, statement_id):
try:
del self.permissions[statement_id]
except KeyError:
raise JsonRESTError("ResourceNotFoundException", "StatementId not found")
def remove_permission(self, event_bus_name, statement_id):
if not event_bus_name:
event_bus_name = "default"
def describe_event_bus(self):
arn = "arn:aws:events:{0}:000000000000:event-bus/default".format(
self.region_name
)
statements = []
for statement_id, data in self.permissions.items():
statements.append(
{
"Sid": statement_id,
"Effect": "Allow",
"Principal": {
"AWS": "arn:aws:iam::{0}:root".format(data["principal"])
},
"Action": data["action"],
"Resource": arn,
}
event_bus = self.describe_event_bus(event_bus_name)
if not len(event_bus._permissions):
raise JsonRESTError(
"ResourceNotFoundException", "EventBus does not have a policy."
)
policy = {"Version": "2012-10-17", "Statement": statements}
policy_json = json.dumps(policy)
return {"Policy": policy_json, "Name": "default", "Arn": arn}
if not event_bus._permissions.pop(statement_id, None):
raise JsonRESTError(
"ResourceNotFoundException",
"Statement with the provided id does not exist.",
)
def describe_event_bus(self, name):
if not name:
name = "default"
event_bus = self.event_buses.get(name)
if not event_bus:
raise JsonRESTError(
"ResourceNotFoundException",
"Event bus {} does not exist.".format(name),
)
return event_bus
def create_event_bus(self, name, event_source_name):
if name in self.event_buses:
raise JsonRESTError(
"ResourceAlreadyExistsException",
"Event bus {} already exists.".format(name),
)
if not event_source_name and "/" in name:
raise JsonRESTError(
"ValidationException", "Event bus name must not contain '/'."
)
if event_source_name and event_source_name not in self.event_sources:
raise JsonRESTError(
"ResourceNotFoundException",
"Event source {} does not exist.".format(event_source_name),
)
self.event_buses[name] = EventBus(self.region_name, name)
return self.event_buses[name]
def list_event_buses(self, name_prefix):
if name_prefix:
return [
event_bus
for event_bus in self.event_buses.values()
if event_bus.name.startswith(name_prefix)
]
return list(self.event_buses.values())
def delete_event_bus(self, name):
if name == "default":
raise JsonRESTError(
"ValidationException", "Cannot delete event bus default."
)
self.event_buses.pop(name, None)
available_regions = boto3.session.Session().get_available_regions("events")

View File

@ -238,20 +238,68 @@ class EventsHandler(BaseResponse):
pass
def put_permission(self):
event_bus_name = self._get_param("EventBusName")
action = self._get_param("Action")
principal = self._get_param("Principal")
statement_id = self._get_param("StatementId")
self.events_backend.put_permission(action, principal, statement_id)
self.events_backend.put_permission(
event_bus_name, action, principal, statement_id
)
return ""
def remove_permission(self):
event_bus_name = self._get_param("EventBusName")
statement_id = self._get_param("StatementId")
self.events_backend.remove_permission(statement_id)
self.events_backend.remove_permission(event_bus_name, statement_id)
return ""
def describe_event_bus(self):
return json.dumps(self.events_backend.describe_event_bus())
name = self._get_param("Name")
event_bus = self.events_backend.describe_event_bus(name)
response = {
"Name": event_bus.name,
"Arn": event_bus.arn,
}
if event_bus.policy:
response["Policy"] = event_bus.policy
return json.dumps(response), self.response_headers
def create_event_bus(self):
name = self._get_param("Name")
event_source_name = self._get_param("EventSourceName")
event_bus = self.events_backend.create_event_bus(name, event_source_name)
return json.dumps({"EventBusArn": event_bus.arn}), self.response_headers
def list_event_buses(self):
name_prefix = self._get_param("NamePrefix")
# ToDo: add 'NextToken' & 'Limit' parameters
response = []
for event_bus in self.events_backend.list_event_buses(name_prefix):
event_bus_response = {
"Name": event_bus.name,
"Arn": event_bus.arn,
}
if event_bus.policy:
event_bus_response["Policy"] = event_bus.policy
response.append(event_bus_response)
return json.dumps({"EventBuses": response}), self.response_headers
def delete_event_bus(self):
name = self._get_param("Name")
self.events_backend.delete_event_bus(name)
return "", self.response_headers

View File

@ -1,6 +1,7 @@
import random
import boto3
import json
import sure # noqa
from moto.events import mock_events
from botocore.exceptions import ClientError
@ -204,6 +205,53 @@ def test_permissions():
assert resp_policy["Statement"][0]["Sid"] == "Account1"
@mock_events
def test_put_permission_errors():
client = boto3.client("events", "us-east-1")
client.create_event_bus(Name="test-bus")
client.put_permission.when.called_with(
EventBusName="non-existing",
Action="events:PutEvents",
Principal="111111111111",
StatementId="test",
).should.throw(ClientError, "Event bus non-existing does not exist.")
client.put_permission.when.called_with(
EventBusName="test-bus",
Action="events:PutPermission",
Principal="111111111111",
StatementId="test",
).should.throw(
ClientError, "Provided value in parameter 'action' is not supported."
)
@mock_events
def test_remove_permission_errors():
client = boto3.client("events", "us-east-1")
client.create_event_bus(Name="test-bus")
client.remove_permission.when.called_with(
EventBusName="non-existing", StatementId="test"
).should.throw(ClientError, "Event bus non-existing does not exist.")
client.remove_permission.when.called_with(
EventBusName="test-bus", StatementId="test"
).should.throw(ClientError, "EventBus does not have a policy.")
client.put_permission(
EventBusName="test-bus",
Action="events:PutEvents",
Principal="111111111111",
StatementId="test",
)
client.remove_permission.when.called_with(
EventBusName="test-bus", StatementId="non-existing"
).should.throw(ClientError, "Statement with the provided id does not exist.")
@mock_events
def test_put_events():
client = boto3.client("events", "eu-central-1")
@ -220,3 +268,177 @@ def test_put_events():
with assert_raises(ClientError):
client.put_events(Entries=[event] * 20)
@mock_events
def test_create_event_bus():
client = boto3.client("events", "us-east-1")
response = client.create_event_bus(Name="test-bus")
response["EventBusArn"].should.equal(
"arn:aws:events:us-east-1:123456789012:event-bus/test-bus"
)
@mock_events
def test_create_event_bus_errors():
client = boto3.client("events", "us-east-1")
client.create_event_bus(Name="test-bus")
client.create_event_bus.when.called_with(Name="test-bus").should.throw(
ClientError, "Event bus test-bus already exists."
)
# the 'default' name is already used for the account's default event bus.
client.create_event_bus.when.called_with(Name="default").should.throw(
ClientError, "Event bus default already exists."
)
# non partner event buses can't contain the '/' character
client.create_event_bus.when.called_with(Name="test/test-bus").should.throw(
ClientError, "Event bus name must not contain '/'."
)
client.create_event_bus.when.called_with(
Name="aws.partner/test/test-bus", EventSourceName="aws.partner/test/test-bus"
).should.throw(
ClientError, "Event source aws.partner/test/test-bus does not exist."
)
@mock_events
def test_describe_event_bus():
client = boto3.client("events", "us-east-1")
response = client.describe_event_bus()
response["Name"].should.equal("default")
response["Arn"].should.equal(
"arn:aws:events:us-east-1:123456789012:event-bus/default"
)
response.should_not.have.key("Policy")
client.create_event_bus(Name="test-bus")
client.put_permission(
EventBusName="test-bus",
Action="events:PutEvents",
Principal="111111111111",
StatementId="test",
)
response = client.describe_event_bus(Name="test-bus")
response["Name"].should.equal("test-bus")
response["Arn"].should.equal(
"arn:aws:events:us-east-1:123456789012:event-bus/test-bus"
)
json.loads(response["Policy"]).should.equal(
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "test",
"Effect": "Allow",
"Principal": {"AWS": "arn:aws:iam::111111111111:root"},
"Action": "events:PutEvents",
"Resource": "arn:aws:events:us-east-1:123456789012:event-bus/test-bus",
}
],
}
)
@mock_events
def test_describe_event_bus_errors():
client = boto3.client("events", "us-east-1")
client.describe_event_bus.when.called_with(Name="non-existing").should.throw(
ClientError, "Event bus non-existing does not exist."
)
@mock_events
def test_list_event_buses():
client = boto3.client("events", "us-east-1")
client.create_event_bus(Name="test-bus-1")
client.create_event_bus(Name="test-bus-2")
client.create_event_bus(Name="other-bus-1")
client.create_event_bus(Name="other-bus-2")
response = client.list_event_buses()
response["EventBuses"].should.have.length_of(5)
sorted(response["EventBuses"], key=lambda i: i["Name"]).should.equal(
[
{
"Name": "default",
"Arn": "arn:aws:events:us-east-1:123456789012:event-bus/default",
},
{
"Name": "other-bus-1",
"Arn": "arn:aws:events:us-east-1:123456789012:event-bus/other-bus-1",
},
{
"Name": "other-bus-2",
"Arn": "arn:aws:events:us-east-1:123456789012:event-bus/other-bus-2",
},
{
"Name": "test-bus-1",
"Arn": "arn:aws:events:us-east-1:123456789012:event-bus/test-bus-1",
},
{
"Name": "test-bus-2",
"Arn": "arn:aws:events:us-east-1:123456789012:event-bus/test-bus-2",
},
]
)
response = client.list_event_buses(NamePrefix="other-bus")
response["EventBuses"].should.have.length_of(2)
sorted(response["EventBuses"], key=lambda i: i["Name"]).should.equal(
[
{
"Name": "other-bus-1",
"Arn": "arn:aws:events:us-east-1:123456789012:event-bus/other-bus-1",
},
{
"Name": "other-bus-2",
"Arn": "arn:aws:events:us-east-1:123456789012:event-bus/other-bus-2",
},
]
)
@mock_events
def test_delete_event_bus():
client = boto3.client("events", "us-east-1")
client.create_event_bus(Name="test-bus")
response = client.list_event_buses()
response["EventBuses"].should.have.length_of(2)
client.delete_event_bus(Name="test-bus")
response = client.list_event_buses()
response["EventBuses"].should.have.length_of(1)
response["EventBuses"].should.equal(
[
{
"Name": "default",
"Arn": "arn:aws:events:us-east-1:123456789012:event-bus/default",
}
]
)
# deleting non existing event bus should be successful
client.delete_event_bus(Name="non-existing")
@mock_events
def test_delete_event_bus_errors():
client = boto3.client("events", "us-east-1")
client.delete_event_bus.when.called_with(Name="default").should.throw(
ClientError, "Cannot delete event bus default."
)