Add EventBridge Archive CRUD endpoints (#3617)

* Add events.create_archive

* Add events.describe_archive

* Add events.list_archives

* Add events.update_archive

* Add events.delete_archive

* Add actual archive functionality

* Fix test

* Fix PR issues
This commit is contained in:
Anton Grübel 2021-01-28 11:47:53 +01:00 committed by GitHub
parent 9e1fcac9d4
commit 199da2220b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 985 additions and 1 deletions

View File

@ -2,6 +2,15 @@ from __future__ import unicode_literals
from moto.core.exceptions import JsonRESTError
class InvalidEventPatternException(JsonRESTError):
code = 400
def __init__(self):
super(InvalidEventPatternException, self).__init__(
"InvalidEventPatternException", "Event pattern is not valid."
)
class ResourceNotFoundException(JsonRESTError):
code = 400
@ -11,6 +20,15 @@ class ResourceNotFoundException(JsonRESTError):
)
class ResourceAlreadyExistsException(JsonRESTError):
code = 400
def __init__(self, message):
super(ResourceAlreadyExistsException, self).__init__(
"ResourceAlreadyExistsException", message
)
class ValidationException(JsonRESTError):
code = 400

View File

@ -1,11 +1,21 @@
import copy
import os
import re
import json
import sys
from datetime import datetime
from boto3 import Session
from moto.core.exceptions import JsonRESTError
from moto.core import ACCOUNT_ID, BaseBackend, CloudFormationModel
from moto.events.exceptions import ValidationException, ResourceNotFoundException
from moto.core.utils import unix_time
from moto.events.exceptions import (
ValidationException,
ResourceNotFoundException,
ResourceAlreadyExistsException,
InvalidEventPatternException,
)
from moto.utilities.tagging_service import TaggingService
from uuid import uuid4
@ -200,6 +210,146 @@ class EventBus(CloudFormationModel):
event_backend.delete_event_bus(event_bus_name)
class Archive(CloudFormationModel):
# https://docs.aws.amazon.com/eventbridge/latest/APIReference/API_ListArchives.html#API_ListArchives_RequestParameters
VALID_STATES = [
"ENABLED",
"DISABLED",
"CREATING",
"UPDATING",
"CREATE_FAILED",
"UPDATE_FAILED",
]
def __init__(
self, region_name, name, source_arn, description, event_pattern, retention
):
self.region = region_name
self.name = name
self.source_arn = source_arn
self.description = description
self.event_pattern = event_pattern
self.retention = retention if retention else 0
self.creation_time = unix_time(datetime.utcnow())
self.state = "ENABLED"
self.events = []
self.event_bus_name = source_arn.split("/")[-1]
@property
def arn(self):
return "arn:aws:events:{region}:{account_id}:archive/{name}".format(
region=self.region, account_id=ACCOUNT_ID, name=self.name
)
def describe_short(self):
return {
"ArchiveName": self.name,
"EventSourceArn": self.source_arn,
"State": self.state,
"RetentionDays": self.retention,
"SizeBytes": sys.getsizeof(self.events) if len(self.events) > 0 else 0,
"EventCount": len(self.events),
"CreationTime": self.creation_time,
}
def describe(self):
result = {
"ArchiveArn": self.arn,
"Description": self.description,
"EventPattern": self.event_pattern,
}
result.update(self.describe_short())
return result
def update(self, description, event_pattern, retention):
if description:
self.description = description
if event_pattern:
self.event_pattern = event_pattern
if retention:
self.retention = retention
def delete(self, region_name):
event_backend = events_backends[region_name]
event_backend.archives.pop(self.name)
def matches_pattern(self, event):
if not self.event_pattern:
return True
# only works on the first level of the event dict
# logic for nested dicts needs to be implemented
for pattern_key, pattern_value in json.loads(self.event_pattern).items():
event_value = event.get(pattern_key)
if event_value not in pattern_value:
return False
def get_cfn_attribute(self, attribute_name):
from moto.cloudformation.exceptions import UnformattedGetAttTemplateException
if attribute_name == "ArchiveName":
return self.name
elif attribute_name == "Arn":
return self.arn
raise UnformattedGetAttTemplateException()
@staticmethod
def cloudformation_name_type():
return "ArchiveName"
@staticmethod
def cloudformation_type():
# https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-events-archive.html
return "AWS::Events::Archive"
@classmethod
def create_from_cloudformation_json(
cls, resource_name, cloudformation_json, region_name
):
properties = cloudformation_json["Properties"]
event_backend = events_backends[region_name]
source_arn = properties.get("SourceArn")
description = properties.get("Description")
event_pattern = properties.get("EventPattern")
retention = properties.get("RetentionDays")
return event_backend.create_archive(
resource_name, source_arn, description, event_pattern, retention
)
@classmethod
def update_from_cloudformation_json(
cls, original_resource, new_resource_name, cloudformation_json, region_name
):
if new_resource_name == original_resource.name:
properties = cloudformation_json["Properties"]
original_resource.update(
properties.get("Description"),
properties.get("EventPattern"),
properties.get("Retention"),
)
return original_resource
else:
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
):
event_backend = events_backends[region_name]
event_backend.delete_archive(resource_name)
class EventsBackend(BaseBackend):
ACCOUNT_ID = re.compile(r"^(\d{1,12}|\*)$")
STATEMENT_ID = re.compile(r"^[a-zA-Z0-9-_]{1,64}$")
@ -213,6 +363,7 @@ class EventsBackend(BaseBackend):
self.region_name = region_name
self.event_buses = {}
self.event_sources = {}
self.archives = {}
self.tagger = TaggingService()
self._add_default_event_bus()
@ -404,6 +555,22 @@ class EventsBackend(BaseBackend):
entries.append({"EventId": str(uuid4())})
# add to correct archive
# if 'EventBusName' is not espically set, it will stored in the default
event_bus_name = event.get("EventBusName", "default")
archives = [
archive
for archive in self.archives.values()
if archive.event_bus_name == event_bus_name
]
for archive in archives:
event_copy = copy.deepcopy(event)
event_copy.pop("EventBusName", None)
if archive.matches_pattern(event):
archive.events.append(event_copy)
# We dont really need to store the events yet
return entries
@ -544,6 +711,118 @@ class EventsBackend(BaseBackend):
"Rule {0} does not exist on EventBus default.".format(name)
)
def create_archive(self, name, source_arn, description, event_pattern, retention):
if len(name) > 48:
raise ValidationException(
" 1 validation error detected: "
"Value '{}' at 'archiveName' failed to satisfy constraint: "
"Member must have length less than or equal to 48".format(name)
)
if event_pattern:
self._validate_event_pattern(event_pattern)
event_bus_name = source_arn.split("/")[-1]
if event_bus_name not in self.event_buses:
raise ResourceNotFoundException(
"Event bus {} does not exist.".format(event_bus_name)
)
if name in self.archives:
raise ResourceAlreadyExistsException(
"Archive {} already exists.".format(name)
)
archive = Archive(
self.region_name, name, source_arn, description, event_pattern, retention
)
self.archives[name] = archive
return archive
def _validate_event_pattern(self, pattern):
try:
json_pattern = json.loads(pattern)
except ValueError: # json.JSONDecodeError exists since Python 3.5
raise InvalidEventPatternException
if not self._is_event_value_an_array(json_pattern):
raise InvalidEventPatternException
def _is_event_value_an_array(self, pattern):
# the values of a key in the event pattern have to be either a dict or an array
for value in pattern.values():
if isinstance(value, dict):
if not self._is_event_value_an_array(value):
return False
elif not isinstance(value, list):
return False
return True
def describe_archive(self, name):
archive = self.archives.get(name)
if not archive:
raise ResourceNotFoundException("Archive {} does not exist.".format(name))
return archive.describe()
def list_archives(self, name_prefix, source_arn, state):
if [name_prefix, source_arn, state].count(None) < 2:
raise ValidationException(
"At most one filter is allowed for ListArchives. "
"Use either : State, EventSourceArn, or NamePrefix."
)
if state and state not in Archive.VALID_STATES:
raise ValidationException(
"1 validation error detected: Value '{0}' at 'state' failed to satisfy constraint: "
"Member must satisfy enum value set: "
"[{1}]".format(state, ", ".join(Archive.VALID_STATES))
)
if [name_prefix, source_arn, state].count(None) == 3:
return [archive.describe_short() for archive in self.archives.values()]
result = []
for archive in self.archives.values():
if name_prefix and archive.name.startswith(name_prefix):
result.append(archive.describe_short())
elif source_arn and archive.source_arn == source_arn:
result.append(archive.describe_short())
elif state and archive.state == state:
result.append(archive.describe_short())
return result
def update_archive(self, name, description, event_pattern, retention):
archive = self.archives.get(name)
if not archive:
raise ResourceNotFoundException("Archive {} does not exist.".format(name))
if event_pattern:
self._validate_event_pattern(event_pattern)
archive.update(description, event_pattern, retention)
return {
"ArchiveArn": archive.arn,
"CreationTime": archive.creation_time,
"State": archive.state,
}
def delete_archive(self, name):
archive = self.archives.get(name)
if not archive:
raise ResourceNotFoundException("Archive {} does not exist.".format(name))
archive.delete(self.region_name)
events_backends = {}
for region in Session().get_available_regions("events"):

View File

@ -332,3 +332,60 @@ class EventsHandler(BaseResponse):
result = self.events_backend.untag_resource(arn, tags)
return json.dumps(result), self.response_headers
def create_archive(self):
name = self._get_param("ArchiveName")
source_arn = self._get_param("EventSourceArn")
description = self._get_param("Description")
event_pattern = self._get_param("EventPattern")
retention = self._get_param("RetentionDays")
archive = self.events_backend.create_archive(
name, source_arn, description, event_pattern, retention
)
return (
json.dumps(
{
"ArchiveArn": archive.arn,
"CreationTime": archive.creation_time,
"State": archive.state,
}
),
self.response_headers,
)
def describe_archive(self):
name = self._get_param("ArchiveName")
result = self.events_backend.describe_archive(name)
return json.dumps(result), self.response_headers
def list_archives(self):
name_prefix = self._get_param("NamePrefix")
source_arn = self._get_param("EventSourceArn")
state = self._get_param("State")
result = self.events_backend.list_archives(name_prefix, source_arn, state)
return json.dumps({"Archives": result}), self.response_headers
def update_archive(self):
name = self._get_param("ArchiveName")
description = self._get_param("Description")
event_pattern = self._get_param("EventPattern")
retention = self._get_param("RetentionDays")
result = self.events_backend.update_archive(
name, description, event_pattern, retention
)
return json.dumps(result), self.response_headers
def delete_archive(self):
name = self._get_param("ArchiveName")
self.events_backend.delete_archive(name)
return "", self.response_headers

View File

@ -1,6 +1,7 @@
import json
import random
import unittest
from datetime import datetime
import boto3
import sure # noqa
@ -748,3 +749,525 @@ def test_list_tags_for_resource_error_unknown_arn():
ex.response["Error"]["Message"].should.equal(
"Rule unknown does not exist on EventBus default."
)
@mock_events
def test_create_archive():
# given
client = boto3.client("events", "eu-central-1")
# when
response = client.create_archive(
ArchiveName="test-archive",
EventSourceArn="arn:aws:events:eu-central-1:{}:event-bus/default".format(
ACCOUNT_ID
),
)
# then
response["ArchiveArn"].should.equal(
"arn:aws:events:eu-central-1:{}:archive/test-archive".format(ACCOUNT_ID)
)
response["CreationTime"].should.be.a(datetime)
response["State"].should.equal("ENABLED")
@mock_events
def test_create_archive_custom_event_bus():
# given
client = boto3.client("events", "eu-central-1")
event_bus_arn = client.create_event_bus(Name="test-bus")["EventBusArn"]
# when
response = client.create_archive(
ArchiveName="test-archive",
EventSourceArn=event_bus_arn,
EventPattern=json.dumps(
{
"key_1": {
"key_2": {"key_3": ["value_1", "value_2"], "key_4": ["value_3"]}
}
}
),
)
# then
response["ArchiveArn"].should.equal(
"arn:aws:events:eu-central-1:{}:archive/test-archive".format(ACCOUNT_ID)
)
response["CreationTime"].should.be.a(datetime)
response["State"].should.equal("ENABLED")
@mock_events
def test_create_archive_error_long_name():
# given
client = boto3.client("events", "eu-central-1")
name = "a" * 49
# when
with pytest.raises(ClientError) as e:
client.create_archive(
ArchiveName=name,
EventSourceArn=(
"arn:aws:events:eu-central-1:{}:event-bus/default".format(ACCOUNT_ID)
),
)
# then
ex = e.value
ex.operation_name.should.equal("CreateArchive")
ex.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
ex.response["Error"]["Code"].should.contain("ValidationException")
ex.response["Error"]["Message"].should.equal(
" 1 validation error detected: "
"Value '{}' at 'archiveName' failed to satisfy constraint: "
"Member must have length less than or equal to 48".format(name)
)
@mock_events
def test_create_archive_error_invalid_event_pattern():
# given
client = boto3.client("events", "eu-central-1")
# when
with pytest.raises(ClientError) as e:
client.create_archive(
ArchiveName="test-archive",
EventSourceArn=(
"arn:aws:events:eu-central-1:{}:event-bus/default".format(ACCOUNT_ID)
),
EventPattern="invalid",
)
# then
ex = e.value
ex.operation_name.should.equal("CreateArchive")
ex.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
ex.response["Error"]["Code"].should.contain("InvalidEventPatternException")
ex.response["Error"]["Message"].should.equal("Event pattern is not valid.")
@mock_events
def test_create_archive_error_invalid_event_pattern_not_an_array():
# given
client = boto3.client("events", "eu-central-1")
# when
with pytest.raises(ClientError) as e:
client.create_archive(
ArchiveName="test-archive",
EventSourceArn=(
"arn:aws:events:eu-central-1:{}:event-bus/default".format(ACCOUNT_ID)
),
EventPattern=json.dumps(
{
"key_1": {
"key_2": {"key_3": ["value_1"]},
"key_4": {"key_5": ["value_2"], "key_6": "value_3"},
}
}
),
)
# then
ex = e.value
ex.operation_name.should.equal("CreateArchive")
ex.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
ex.response["Error"]["Code"].should.contain("InvalidEventPatternException")
ex.response["Error"]["Message"].should.equal("Event pattern is not valid.")
@mock_events
def test_create_archive_error_unknown_event_bus():
# given
client = boto3.client("events", "eu-central-1")
event_bus_name = "unknown"
# when
with pytest.raises(ClientError) as e:
client.create_archive(
ArchiveName="test-archive",
EventSourceArn=(
"arn:aws:events:eu-central-1:{}:event-bus/{}".format(
ACCOUNT_ID, event_bus_name
)
),
)
# then
ex = e.value
ex.operation_name.should.equal("CreateArchive")
ex.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
ex.response["Error"]["Code"].should.contain("ResourceNotFoundException")
ex.response["Error"]["Message"].should.equal(
"Event bus {} does not exist.".format(event_bus_name)
)
@mock_events
def test_create_archive_error_duplicate():
# given
client = boto3.client("events", "eu-central-1")
name = "test-archive"
source_arn = "arn:aws:events:eu-central-1:{}:event-bus/default".format(ACCOUNT_ID)
client.create_archive(ArchiveName=name, EventSourceArn=source_arn)
# when
with pytest.raises(ClientError) as e:
client.create_archive(ArchiveName=name, EventSourceArn=source_arn)
# then
ex = e.value
ex.operation_name.should.equal("CreateArchive")
ex.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
ex.response["Error"]["Code"].should.contain("ResourceAlreadyExistsException")
ex.response["Error"]["Message"].should.equal("Archive test-archive already exists.")
@mock_events
def test_describe_archive():
# given
client = boto3.client("events", "eu-central-1")
name = "test-archive"
source_arn = "arn:aws:events:eu-central-1:{}:event-bus/default".format(ACCOUNT_ID)
event_pattern = json.dumps({"key": ["value"]})
client.create_archive(
ArchiveName=name,
EventSourceArn=source_arn,
Description="test archive",
EventPattern=event_pattern,
)
# when
response = client.describe_archive(ArchiveName=name)
# then
response["ArchiveArn"].should.equal(
"arn:aws:events:eu-central-1:{0}:archive/{1}".format(ACCOUNT_ID, name)
)
response["ArchiveName"].should.equal(name)
response["CreationTime"].should.be.a(datetime)
response["Description"].should.equal("test archive")
response["EventCount"].should.equal(0)
response["EventPattern"].should.equal(event_pattern)
response["EventSourceArn"].should.equal(source_arn)
response["RetentionDays"].should.equal(0)
response["SizeBytes"].should.equal(0)
response["State"].should.equal("ENABLED")
@mock_events
def test_describe_archive_error_unknown_archive():
# given
client = boto3.client("events", "eu-central-1")
name = "unknown"
# when
with pytest.raises(ClientError) as e:
client.describe_archive(ArchiveName=name)
# then
ex = e.value
ex.operation_name.should.equal("DescribeArchive")
ex.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
ex.response["Error"]["Code"].should.contain("ResourceNotFoundException")
ex.response["Error"]["Message"].should.equal(
"Archive {} does not exist.".format(name)
)
@mock_events
def test_list_archives():
# given
client = boto3.client("events", "eu-central-1")
name = "test-archive"
source_arn = "arn:aws:events:eu-central-1:{}:event-bus/default".format(ACCOUNT_ID)
event_pattern = json.dumps({"key": ["value"]})
client.create_archive(
ArchiveName=name,
EventSourceArn=source_arn,
Description="test archive",
EventPattern=event_pattern,
)
# when
archives = client.list_archives()["Archives"]
# then
archives.should.have.length_of(1)
archive = archives[0]
archive["ArchiveName"].should.equal(name)
archive["CreationTime"].should.be.a(datetime)
archive["EventCount"].should.equal(0)
archive["EventSourceArn"].should.equal(source_arn)
archive["RetentionDays"].should.equal(0)
archive["SizeBytes"].should.equal(0)
archive["State"].should.equal("ENABLED")
archive.should_not.have.key("ArchiveArn")
archive.should_not.have.key("Description")
archive.should_not.have.key("EventPattern")
@mock_events
def test_list_archives_with_name_prefix():
# given
client = boto3.client("events", "eu-central-1")
source_arn = "arn:aws:events:eu-central-1:{}:event-bus/default".format(ACCOUNT_ID)
client.create_archive(
ArchiveName="test", EventSourceArn=source_arn,
)
client.create_archive(
ArchiveName="test-archive", EventSourceArn=source_arn,
)
# when
archives = client.list_archives(NamePrefix="test-")["Archives"]
# then
archives.should.have.length_of(1)
archives[0]["ArchiveName"].should.equal("test-archive")
@mock_events
def test_list_archives_with_source_arn():
# given
client = boto3.client("events", "eu-central-1")
source_arn = "arn:aws:events:eu-central-1:{}:event-bus/default".format(ACCOUNT_ID)
source_arn_2 = client.create_event_bus(Name="test-bus")["EventBusArn"]
client.create_archive(
ArchiveName="test", EventSourceArn=source_arn,
)
client.create_archive(
ArchiveName="test-archive", EventSourceArn=source_arn_2,
)
# when
archives = client.list_archives(EventSourceArn=source_arn)["Archives"]
# then
archives.should.have.length_of(1)
archives[0]["ArchiveName"].should.equal("test")
@mock_events
def test_list_archives_with_state():
# given
client = boto3.client("events", "eu-central-1")
source_arn = "arn:aws:events:eu-central-1:{}:event-bus/default".format(ACCOUNT_ID)
client.create_archive(
ArchiveName="test", EventSourceArn=source_arn,
)
client.create_archive(
ArchiveName="test-archive", EventSourceArn=source_arn,
)
# when
archives = client.list_archives(State="DISABLED")["Archives"]
# then
archives.should.have.length_of(0)
@mock_events
def test_list_archives_error_multiple_filters():
# given
client = boto3.client("events", "eu-central-1")
# when
with pytest.raises(ClientError) as e:
client.list_archives(NamePrefix="test", State="ENABLED")
# then
ex = e.value
ex.operation_name.should.equal("ListArchives")
ex.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
ex.response["Error"]["Code"].should.contain("ValidationException")
ex.response["Error"]["Message"].should.equal(
"At most one filter is allowed for ListArchives. "
"Use either : State, EventSourceArn, or NamePrefix."
)
@mock_events
def test_list_archives_error_invalid_state():
# given
client = boto3.client("events", "eu-central-1")
# when
with pytest.raises(ClientError) as e:
client.list_archives(State="invalid")
# then
ex = e.value
ex.operation_name.should.equal("ListArchives")
ex.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
ex.response["Error"]["Code"].should.contain("ValidationException")
ex.response["Error"]["Message"].should.equal(
"1 validation error detected: Value 'invalid' at 'state' failed to satisfy constraint: "
"Member must satisfy enum value set: "
"[ENABLED, DISABLED, CREATING, UPDATING, CREATE_FAILED, UPDATE_FAILED]"
)
@mock_events
def test_update_archive():
# given
client = boto3.client("events", "eu-central-1")
name = "test-archive"
source_arn = "arn:aws:events:eu-central-1:{}:event-bus/default".format(ACCOUNT_ID)
event_pattern = json.dumps({"key": ["value"]})
archive_arn = client.create_archive(ArchiveName=name, EventSourceArn=source_arn)[
"ArchiveArn"
]
# when
response = client.update_archive(
ArchiveName=name,
Description="test archive",
EventPattern=event_pattern,
RetentionDays=14,
)
# then
response["ArchiveArn"].should.equal(archive_arn)
response["State"].should.equal("ENABLED")
creation_time = response["CreationTime"]
creation_time.should.be.a(datetime)
response = client.describe_archive(ArchiveName=name)
response["ArchiveArn"].should.equal(archive_arn)
response["ArchiveName"].should.equal(name)
response["CreationTime"].should.equal(creation_time)
response["Description"].should.equal("test archive")
response["EventCount"].should.equal(0)
response["EventPattern"].should.equal(event_pattern)
response["EventSourceArn"].should.equal(source_arn)
response["RetentionDays"].should.equal(14)
response["SizeBytes"].should.equal(0)
response["State"].should.equal("ENABLED")
@mock_events
def test_update_archive_error_invalid_event_pattern():
# given
client = boto3.client("events", "eu-central-1")
name = "test-archive"
client.create_archive(
ArchiveName=name,
EventSourceArn="arn:aws:events:eu-central-1:{}:event-bus/default".format(
ACCOUNT_ID
),
)
# when
with pytest.raises(ClientError) as e:
client.update_archive(
ArchiveName=name, EventPattern="invalid",
)
# then
ex = e.value
ex.operation_name.should.equal("UpdateArchive")
ex.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
ex.response["Error"]["Code"].should.contain("InvalidEventPatternException")
ex.response["Error"]["Message"].should.equal("Event pattern is not valid.")
@mock_events
def test_update_archive_error_unknown_archive():
# given
client = boto3.client("events", "eu-central-1")
name = "unknown"
# when
with pytest.raises(ClientError) as e:
client.update_archive(ArchiveName=name)
# then
ex = e.value
ex.operation_name.should.equal("UpdateArchive")
ex.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
ex.response["Error"]["Code"].should.contain("ResourceNotFoundException")
ex.response["Error"]["Message"].should.equal(
"Archive {} does not exist.".format(name)
)
@mock_events
def test_delete_archive():
# given
client = boto3.client("events", "eu-central-1")
name = "test-archive"
client.create_archive(
ArchiveName=name,
EventSourceArn="arn:aws:events:eu-central-1:{}:event-bus/default".format(
ACCOUNT_ID
),
)
# when
client.delete_archive(ArchiveName=name)
# then
response = client.list_archives(NamePrefix="test")["Archives"]
response.should.have.length_of(0)
@mock_events
def test_delete_archive_error_unknown_archive():
# given
client = boto3.client("events", "eu-central-1")
name = "unknown"
# when
with pytest.raises(ClientError) as e:
client.delete_archive(ArchiveName=name)
# then
ex = e.value
ex.operation_name.should.equal("DeleteArchive")
ex.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
ex.response["Error"]["Code"].should.contain("ResourceNotFoundException")
ex.response["Error"]["Message"].should.equal(
"Archive {} does not exist.".format(name)
)
@mock_events
def test_archive_actual_events():
# given
client = boto3.client("events", "eu-central-1")
name = "test-archive"
name_2 = "test-archive-2"
event_bus_arn = "arn:aws:events:eu-central-1:{}:event-bus/default".format(
ACCOUNT_ID
)
event = {
"Source": "source",
"DetailType": "type",
"Detail": '{ "key1": "value1" }',
}
client.create_archive(ArchiveName=name, EventSourceArn=event_bus_arn)
client.create_archive(
ArchiveName=name_2,
EventSourceArn=event_bus_arn,
EventPattern=json.dumps({"DetailType": ["type"], "Source": ["test"]}),
)
# when
response = client.put_events(Entries=[event])
# then
response["FailedEntryCount"].should.equal(0)
response["Entries"].should.have.length_of(1)
response = client.describe_archive(ArchiveName=name)
response["EventCount"].should.equal(1)
response["SizeBytes"].should.be.greater_than(0)
response = client.describe_archive(ArchiveName=name_2)
response["EventCount"].should.equal(0)
response["SizeBytes"].should.equal(0)

View File

@ -0,0 +1,107 @@
import copy
from string import Template
import boto3
import json
from moto import mock_cloudformation, mock_events
import sure # noqa
from moto.core import ACCOUNT_ID
archive_template = Template(
json.dumps(
{
"AWSTemplateFormatVersion": "2010-09-09",
"Description": "EventBridge Archive Test",
"Resources": {
"Archive": {
"Type": "AWS::Events::Archive",
"Properties": {
"ArchiveName": "${archive_name}",
"SourceArn": {
"Fn::Sub": "arn:aws:events:$${AWS::Region}:$${AWS::AccountId}:event-bus/default"
},
},
}
},
"Outputs": {
"Arn": {
"Description": "Archive Arn",
"Value": {"Fn::GetAtt": ["Archive", "Arn"]},
}
},
}
)
)
@mock_events
@mock_cloudformation
def test_create_archive():
# given
cfn_client = boto3.client("cloudformation", region_name="eu-central-1")
name = "test-archive"
stack_name = "test-stack"
template = archive_template.substitute({"archive_name": name})
# when
cfn_client.create_stack(StackName=stack_name, TemplateBody=template)
# then
archive_arn = "arn:aws:events:eu-central-1:{0}:archive/{1}".format(ACCOUNT_ID, name)
stack = cfn_client.describe_stacks(StackName=stack_name)["Stacks"][0]
stack["Outputs"][0]["OutputValue"].should.equal(archive_arn)
events_client = boto3.client("events", region_name="eu-central-1")
response = events_client.describe_archive(ArchiveName=name)
response["ArchiveArn"].should.equal(archive_arn)
@mock_events
@mock_cloudformation
def test_update_archive():
# given
cfn_client = boto3.client("cloudformation", region_name="eu-central-1")
name = "test-archive"
stack_name = "test-stack"
template = archive_template.substitute({"archive_name": name})
cfn_client.create_stack(StackName=stack_name, TemplateBody=template)
template_update = copy.deepcopy(json.loads(template))
template_update["Resources"]["Archive"]["Properties"][
"Description"
] = "test archive"
# when
cfn_client.update_stack(
StackName=stack_name, TemplateBody=json.dumps(template_update)
)
# then
events_client = boto3.client("events", region_name="eu-central-1")
response = events_client.describe_archive(ArchiveName=name)
response["ArchiveArn"].should.equal(
"arn:aws:events:eu-central-1:{0}:archive/{1}".format(ACCOUNT_ID, name)
)
response["Description"].should.equal("test archive")
@mock_events
@mock_cloudformation
def test_delete_archive():
# given
cfn_client = boto3.client("cloudformation", region_name="eu-central-1")
name = "test-archive"
stack_name = "test-stack"
template = archive_template.substitute({"archive_name": name})
cfn_client.create_stack(StackName=stack_name, TemplateBody=template)
# when
cfn_client.delete_stack(StackName=stack_name)
# then
events_client = boto3.client("events", region_name="eu-central-1")
response = events_client.list_archives(NamePrefix="test")["Archives"]
response.should.have.length_of(0)