1858 lines
		
	
	
		
			60 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			1858 lines
		
	
	
		
			60 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
import copy
 | 
						|
import os
 | 
						|
import re
 | 
						|
import json
 | 
						|
import sys
 | 
						|
import warnings
 | 
						|
from collections import namedtuple
 | 
						|
from datetime import datetime
 | 
						|
from enum import Enum, unique
 | 
						|
from json import JSONDecodeError
 | 
						|
from operator import lt, le, eq, ge, gt
 | 
						|
 | 
						|
from collections import OrderedDict
 | 
						|
from moto.core.exceptions import JsonRESTError
 | 
						|
from moto.core import BaseBackend, BackendDict, CloudFormationModel, BaseModel
 | 
						|
from moto.core.utils import (
 | 
						|
    unix_time,
 | 
						|
    unix_time_millis,
 | 
						|
    iso_8601_datetime_without_milliseconds,
 | 
						|
)
 | 
						|
from moto.events.exceptions import (
 | 
						|
    ValidationException,
 | 
						|
    ResourceNotFoundException,
 | 
						|
    ResourceAlreadyExistsException,
 | 
						|
    InvalidEventPatternException,
 | 
						|
    IllegalStatusException,
 | 
						|
)
 | 
						|
from moto.moto_api._internal import mock_random as random
 | 
						|
from moto.utilities.paginator import paginate
 | 
						|
from moto.utilities.tagging_service import TaggingService
 | 
						|
 | 
						|
from .utils import PAGINATION_MODEL
 | 
						|
 | 
						|
# Sentinel to signal the absence of a field for `Exists` pattern matching
 | 
						|
UNDEFINED = object()
 | 
						|
 | 
						|
 | 
						|
class Rule(CloudFormationModel):
 | 
						|
    Arn = namedtuple("Arn", ["service", "resource_type", "resource_id"])
 | 
						|
 | 
						|
    def __init__(
 | 
						|
        self,
 | 
						|
        name,
 | 
						|
        account_id,
 | 
						|
        region_name,
 | 
						|
        description,
 | 
						|
        event_pattern,
 | 
						|
        schedule_exp,
 | 
						|
        role_arn,
 | 
						|
        event_bus_name,
 | 
						|
        state,
 | 
						|
        managed_by=None,
 | 
						|
        targets=None,
 | 
						|
    ):
 | 
						|
        self.name = name
 | 
						|
        self.account_id = account_id
 | 
						|
        self.region_name = region_name
 | 
						|
        self.description = description
 | 
						|
        self.event_pattern = EventPattern.load(event_pattern)
 | 
						|
        self.scheduled_expression = schedule_exp
 | 
						|
        self.role_arn = role_arn
 | 
						|
        self.event_bus_name = event_bus_name
 | 
						|
        self.state = state or "ENABLED"
 | 
						|
        self.managed_by = managed_by  # can only be set by AWS services
 | 
						|
        self.created_by = account_id
 | 
						|
        self.targets = targets or []
 | 
						|
 | 
						|
    @property
 | 
						|
    def arn(self):
 | 
						|
        event_bus_name = (
 | 
						|
            ""
 | 
						|
            if self.event_bus_name == "default"
 | 
						|
            else "{}/".format(self.event_bus_name)
 | 
						|
        )
 | 
						|
 | 
						|
        return (
 | 
						|
            "arn:aws:events:{region}:{account_id}:rule/{event_bus_name}{name}".format(
 | 
						|
                region=self.region_name,
 | 
						|
                account_id=self.account_id,
 | 
						|
                event_bus_name=event_bus_name,
 | 
						|
                name=self.name,
 | 
						|
            )
 | 
						|
        )
 | 
						|
 | 
						|
    @property
 | 
						|
    def physical_resource_id(self):
 | 
						|
        return self.name
 | 
						|
 | 
						|
    # 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):
 | 
						|
        for i in range(0, len(self.targets)):
 | 
						|
            if target_id == self.targets[i]["Id"]:
 | 
						|
                return i
 | 
						|
        return None
 | 
						|
 | 
						|
    def enable(self):
 | 
						|
        self.state = "ENABLED"
 | 
						|
 | 
						|
    def disable(self):
 | 
						|
        self.state = "DISABLED"
 | 
						|
 | 
						|
    def delete(self, account_id, region_name):
 | 
						|
        event_backend = events_backends[account_id][region_name]
 | 
						|
        event_backend.delete_rule(name=self.name)
 | 
						|
 | 
						|
    def put_targets(self, targets):
 | 
						|
        # Not testing for valid ARNs.
 | 
						|
        for target in targets:
 | 
						|
            index = self._check_target_exists(target["Id"])
 | 
						|
            if index is not None:
 | 
						|
                self.targets[index] = target
 | 
						|
            else:
 | 
						|
                self.targets.append(target)
 | 
						|
 | 
						|
    def remove_targets(self, ids):
 | 
						|
        for target_id in ids:
 | 
						|
            index = self._check_target_exists(target_id)
 | 
						|
            if index is not None:
 | 
						|
                self.targets.pop(index)
 | 
						|
 | 
						|
    def send_to_targets(self, event_bus_name, event):
 | 
						|
        event_bus_name = event_bus_name.split("/")[-1]
 | 
						|
        if event_bus_name != self.event_bus_name.split("/")[-1]:
 | 
						|
            return
 | 
						|
 | 
						|
        if not self.event_pattern.matches_event(event):
 | 
						|
            return
 | 
						|
 | 
						|
        # supported targets
 | 
						|
        # - CloudWatch Log Group
 | 
						|
        # - EventBridge Archive
 | 
						|
        # - SQS Queue + FIFO Queue
 | 
						|
        for target in self.targets:
 | 
						|
            arn = self._parse_arn(target["Arn"])
 | 
						|
 | 
						|
            if arn.service == "logs" and arn.resource_type == "log-group":
 | 
						|
                self._send_to_cw_log_group(arn.resource_id, event)
 | 
						|
            elif arn.service == "events" and not arn.resource_type:
 | 
						|
                input_template = json.loads(target["InputTransformer"]["InputTemplate"])
 | 
						|
                archive_arn = self._parse_arn(input_template["archive-arn"])
 | 
						|
 | 
						|
                self._send_to_events_archive(archive_arn.resource_id, event)
 | 
						|
            elif arn.service == "sqs":
 | 
						|
                group_id = target.get("SqsParameters", {}).get("MessageGroupId")
 | 
						|
                self._send_to_sqs_queue(arn.resource_id, event, group_id)
 | 
						|
            else:
 | 
						|
                raise NotImplementedError("Expr not defined for {0}".format(type(self)))
 | 
						|
 | 
						|
    def _parse_arn(self, arn):
 | 
						|
        # http://docs.aws.amazon.com/general/latest/gr/aws-arns-and-namespaces.html
 | 
						|
        # this method needs probably some more fine tuning,
 | 
						|
        # when also other targets are supported
 | 
						|
        elements = arn.split(":", 5)
 | 
						|
 | 
						|
        service = elements[2]
 | 
						|
        resource = elements[5]
 | 
						|
 | 
						|
        if ":" in resource and "/" in resource:
 | 
						|
            if resource.index(":") < resource.index("/"):
 | 
						|
                resource_type, resource_id = resource.split(":", 1)
 | 
						|
            else:
 | 
						|
                resource_type, resource_id = resource.split("/", 1)
 | 
						|
        elif ":" in resource:
 | 
						|
            resource_type, resource_id = resource.split(":", 1)
 | 
						|
        elif "/" in resource:
 | 
						|
            resource_type, resource_id = resource.split("/", 1)
 | 
						|
        else:
 | 
						|
            resource_type = None
 | 
						|
            resource_id = resource
 | 
						|
 | 
						|
        return self.Arn(
 | 
						|
            service=service, resource_type=resource_type, resource_id=resource_id
 | 
						|
        )
 | 
						|
 | 
						|
    def _send_to_cw_log_group(self, name, event):
 | 
						|
        from moto.logs import logs_backends
 | 
						|
 | 
						|
        event_copy = copy.deepcopy(event)
 | 
						|
        event_copy["time"] = iso_8601_datetime_without_milliseconds(
 | 
						|
            datetime.utcfromtimestamp(event_copy["time"])
 | 
						|
        )
 | 
						|
 | 
						|
        log_stream_name = str(random.uuid4())
 | 
						|
        log_events = [
 | 
						|
            {"timestamp": unix_time_millis(), "message": json.dumps(event_copy)}
 | 
						|
        ]
 | 
						|
 | 
						|
        log_backend = logs_backends[self.account_id][self.region_name]
 | 
						|
        log_backend.create_log_stream(name, log_stream_name)
 | 
						|
        log_backend.put_log_events(name, log_stream_name, log_events)
 | 
						|
 | 
						|
    def _send_to_events_archive(self, resource_id, event):
 | 
						|
        archive_name, archive_uuid = resource_id.split(":")
 | 
						|
        archive = events_backends[self.account_id][self.region_name].archives.get(
 | 
						|
            archive_name
 | 
						|
        )
 | 
						|
        if archive.uuid == archive_uuid:
 | 
						|
            archive.events.append(event)
 | 
						|
 | 
						|
    def _send_to_sqs_queue(self, resource_id, event, group_id=None):
 | 
						|
        from moto.sqs import sqs_backends
 | 
						|
 | 
						|
        event_copy = copy.deepcopy(event)
 | 
						|
        event_copy["time"] = iso_8601_datetime_without_milliseconds(
 | 
						|
            datetime.utcfromtimestamp(event_copy["time"])
 | 
						|
        )
 | 
						|
 | 
						|
        if group_id:
 | 
						|
            queue_attr = sqs_backends[self.account_id][
 | 
						|
                self.region_name
 | 
						|
            ].get_queue_attributes(
 | 
						|
                queue_name=resource_id, attribute_names=["ContentBasedDeduplication"]
 | 
						|
            )
 | 
						|
            if queue_attr["ContentBasedDeduplication"] == "false":
 | 
						|
                warnings.warn(
 | 
						|
                    "To let EventBridge send messages to your SQS FIFO queue, "
 | 
						|
                    "you must enable content-based deduplication."
 | 
						|
                )
 | 
						|
                return
 | 
						|
 | 
						|
        sqs_backends[self.account_id][self.region_name].send_message(
 | 
						|
            queue_name=resource_id,
 | 
						|
            message_body=json.dumps(event_copy),
 | 
						|
            group_id=group_id,
 | 
						|
        )
 | 
						|
 | 
						|
    @classmethod
 | 
						|
    def has_cfn_attr(cls, attr):
 | 
						|
        return attr in ["Arn"]
 | 
						|
 | 
						|
    def get_cfn_attribute(self, attribute_name):
 | 
						|
        from moto.cloudformation.exceptions import UnformattedGetAttTemplateException
 | 
						|
 | 
						|
        if attribute_name == "Arn":
 | 
						|
            return self.arn
 | 
						|
 | 
						|
        raise UnformattedGetAttTemplateException()
 | 
						|
 | 
						|
    @staticmethod
 | 
						|
    def cloudformation_name_type():
 | 
						|
        return "Name"
 | 
						|
 | 
						|
    @staticmethod
 | 
						|
    def cloudformation_type():
 | 
						|
        # https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-events-rule.html
 | 
						|
        return "AWS::Events::Rule"
 | 
						|
 | 
						|
    @classmethod
 | 
						|
    def create_from_cloudformation_json(
 | 
						|
        cls, resource_name, cloudformation_json, account_id, region_name, **kwargs
 | 
						|
    ):
 | 
						|
        properties = cloudformation_json["Properties"]
 | 
						|
        properties.setdefault("EventBusName", "default")
 | 
						|
 | 
						|
        if "EventPattern" in properties:
 | 
						|
            properties["EventPattern"] = json.dumps(properties["EventPattern"])
 | 
						|
 | 
						|
        event_name = resource_name
 | 
						|
 | 
						|
        event_pattern = properties.get("EventPattern")
 | 
						|
        scheduled_expression = properties.get("ScheduleExpression")
 | 
						|
        state = properties.get("State")
 | 
						|
        desc = properties.get("Description")
 | 
						|
        role_arn = properties.get("RoleArn")
 | 
						|
        event_bus_name = properties.get("EventBusName")
 | 
						|
        tags = properties.get("Tags")
 | 
						|
 | 
						|
        backend = events_backends[account_id][region_name]
 | 
						|
        return backend.put_rule(
 | 
						|
            event_name,
 | 
						|
            scheduled_expression=scheduled_expression,
 | 
						|
            event_pattern=event_pattern,
 | 
						|
            state=state,
 | 
						|
            description=desc,
 | 
						|
            role_arn=role_arn,
 | 
						|
            event_bus_name=event_bus_name,
 | 
						|
            tags=tags,
 | 
						|
        )
 | 
						|
 | 
						|
    @classmethod
 | 
						|
    def update_from_cloudformation_json(
 | 
						|
        cls,
 | 
						|
        original_resource,
 | 
						|
        new_resource_name,
 | 
						|
        cloudformation_json,
 | 
						|
        account_id,
 | 
						|
        region_name,
 | 
						|
    ):
 | 
						|
        original_resource.delete(account_id, region_name)
 | 
						|
        return cls.create_from_cloudformation_json(
 | 
						|
            new_resource_name, cloudformation_json, account_id, region_name
 | 
						|
        )
 | 
						|
 | 
						|
    @classmethod
 | 
						|
    def delete_from_cloudformation_json(
 | 
						|
        cls, resource_name, cloudformation_json, account_id, region_name
 | 
						|
    ):
 | 
						|
        event_backend = events_backends[account_id][region_name]
 | 
						|
        event_backend.delete_rule(resource_name)
 | 
						|
 | 
						|
    def describe(self):
 | 
						|
        attributes = {
 | 
						|
            "Arn": self.arn,
 | 
						|
            "CreatedBy": self.created_by,
 | 
						|
            "Description": self.description,
 | 
						|
            "EventBusName": self.event_bus_name,
 | 
						|
            "EventPattern": self.event_pattern.dump(),
 | 
						|
            "ManagedBy": self.managed_by,
 | 
						|
            "Name": self.name,
 | 
						|
            "RoleArn": self.role_arn,
 | 
						|
            "ScheduleExpression": self.scheduled_expression,
 | 
						|
            "State": self.state,
 | 
						|
        }
 | 
						|
        attributes = {
 | 
						|
            attr: value for attr, value in attributes.items() if value is not None
 | 
						|
        }
 | 
						|
        return attributes
 | 
						|
 | 
						|
 | 
						|
class EventBus(CloudFormationModel):
 | 
						|
    def __init__(self, account_id, region_name, name, tags=None):
 | 
						|
        self.account_id = account_id
 | 
						|
        self.region = region_name
 | 
						|
        self.name = name
 | 
						|
        self.arn = f"arn:aws:events:{self.region}:{account_id}:event-bus/{name}"
 | 
						|
        self.tags = tags or []
 | 
						|
 | 
						|
        self._statements = {}
 | 
						|
 | 
						|
    @property
 | 
						|
    def policy(self):
 | 
						|
        if self._statements:
 | 
						|
            policy = {
 | 
						|
                "Version": "2012-10-17",
 | 
						|
                "Statement": [stmt.describe() for stmt in self._statements.values()],
 | 
						|
            }
 | 
						|
            return json.dumps(policy)
 | 
						|
        return None
 | 
						|
 | 
						|
    def has_permissions(self):
 | 
						|
        return len(self._statements) > 0
 | 
						|
 | 
						|
    def delete(self, account_id, region_name):
 | 
						|
        event_backend = events_backends[account_id][region_name]
 | 
						|
        event_backend.delete_event_bus(name=self.name)
 | 
						|
 | 
						|
    @classmethod
 | 
						|
    def has_cfn_attr(cls, attr):
 | 
						|
        return attr in ["Arn", "Name", "Policy"]
 | 
						|
 | 
						|
    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()
 | 
						|
 | 
						|
    @staticmethod
 | 
						|
    def cloudformation_name_type():
 | 
						|
        return "Name"
 | 
						|
 | 
						|
    @staticmethod
 | 
						|
    def cloudformation_type():
 | 
						|
        # https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-events-eventbus.html
 | 
						|
        return "AWS::Events::EventBus"
 | 
						|
 | 
						|
    @classmethod
 | 
						|
    def create_from_cloudformation_json(
 | 
						|
        cls, resource_name, cloudformation_json, account_id, region_name, **kwargs
 | 
						|
    ):
 | 
						|
        properties = cloudformation_json["Properties"]
 | 
						|
        event_backend = events_backends[account_id][region_name]
 | 
						|
        event_name = resource_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,
 | 
						|
        account_id,
 | 
						|
        region_name,
 | 
						|
    ):
 | 
						|
        original_resource.delete(account_id, region_name)
 | 
						|
        return cls.create_from_cloudformation_json(
 | 
						|
            new_resource_name, cloudformation_json, account_id, region_name
 | 
						|
        )
 | 
						|
 | 
						|
    @classmethod
 | 
						|
    def delete_from_cloudformation_json(
 | 
						|
        cls, resource_name, cloudformation_json, account_id, region_name
 | 
						|
    ):
 | 
						|
        event_backend = events_backends[account_id][region_name]
 | 
						|
        event_bus_name = resource_name
 | 
						|
        event_backend.delete_event_bus(event_bus_name)
 | 
						|
 | 
						|
    def _remove_principals_statements(self, *principals):
 | 
						|
        statements_to_delete = set()
 | 
						|
 | 
						|
        for principal in principals:
 | 
						|
            for sid, statement in self._statements.items():
 | 
						|
                if statement.principal == principal:
 | 
						|
                    statements_to_delete.add(sid)
 | 
						|
 | 
						|
        # This is done separately to avoid:
 | 
						|
        # RuntimeError: dictionary changed size during iteration
 | 
						|
        for sid in statements_to_delete:
 | 
						|
            del self._statements[sid]
 | 
						|
 | 
						|
    def add_permission(self, statement_id, action, principal, condition):
 | 
						|
        self._remove_principals_statements(principal)
 | 
						|
        statement = EventBusPolicyStatement(
 | 
						|
            sid=statement_id,
 | 
						|
            action=action,
 | 
						|
            principal=principal,
 | 
						|
            condition=condition,
 | 
						|
            resource=self.arn,
 | 
						|
        )
 | 
						|
        self._statements[statement_id] = statement
 | 
						|
 | 
						|
    def add_policy(self, policy):
 | 
						|
        policy_statements = policy["Statement"]
 | 
						|
 | 
						|
        principals = [stmt["Principal"] for stmt in policy_statements]
 | 
						|
        self._remove_principals_statements(*principals)
 | 
						|
 | 
						|
        for new_statement in policy_statements:
 | 
						|
            sid = new_statement["Sid"]
 | 
						|
            self._statements[sid] = EventBusPolicyStatement.from_dict(new_statement)
 | 
						|
 | 
						|
    def remove_statement(self, sid):
 | 
						|
        return self._statements.pop(sid, None)
 | 
						|
 | 
						|
    def remove_statements(self):
 | 
						|
        self._statements.clear()
 | 
						|
 | 
						|
 | 
						|
class EventBusPolicyStatement:
 | 
						|
    def __init__(
 | 
						|
        self, sid, principal, action, resource, effect="Allow", condition=None
 | 
						|
    ):
 | 
						|
        self.sid = sid
 | 
						|
        self.principal = principal
 | 
						|
        self.action = action
 | 
						|
        self.resource = resource
 | 
						|
        self.effect = effect
 | 
						|
        self.condition = condition
 | 
						|
 | 
						|
    def describe(self):
 | 
						|
        statement = dict(
 | 
						|
            Sid=self.sid,
 | 
						|
            Effect=self.effect,
 | 
						|
            Principal=self.principal,
 | 
						|
            Action=self.action,
 | 
						|
            Resource=self.resource,
 | 
						|
        )
 | 
						|
 | 
						|
        if self.condition:
 | 
						|
            statement["Condition"] = self.condition
 | 
						|
        return statement
 | 
						|
 | 
						|
    @classmethod
 | 
						|
    def from_dict(cls, statement_dict):
 | 
						|
        params = dict(
 | 
						|
            sid=statement_dict["Sid"],
 | 
						|
            effect=statement_dict["Effect"],
 | 
						|
            principal=statement_dict["Principal"],
 | 
						|
            action=statement_dict["Action"],
 | 
						|
            resource=statement_dict["Resource"],
 | 
						|
        )
 | 
						|
        condition = statement_dict.get("Condition")
 | 
						|
        if condition:
 | 
						|
            params["condition"] = condition
 | 
						|
 | 
						|
        return cls(**params)
 | 
						|
 | 
						|
 | 
						|
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,
 | 
						|
        account_id,
 | 
						|
        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 = EventPattern.load(event_pattern)
 | 
						|
        self.retention = retention if retention else 0
 | 
						|
 | 
						|
        self.arn = f"arn:aws:events:{region_name}:{account_id}:archive/{name}"
 | 
						|
        self.creation_time = unix_time(datetime.utcnow())
 | 
						|
        self.state = "ENABLED"
 | 
						|
        self.uuid = str(random.uuid4())
 | 
						|
 | 
						|
        self.events = []
 | 
						|
        self.event_bus_name = source_arn.split("/")[-1]
 | 
						|
 | 
						|
    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.dump(),
 | 
						|
        }
 | 
						|
        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 = EventPattern.load(event_pattern)
 | 
						|
        if retention:
 | 
						|
            self.retention = retention
 | 
						|
 | 
						|
    def delete(self, account_id, region_name):
 | 
						|
        event_backend = events_backends[account_id][region_name]
 | 
						|
        event_backend.archives.pop(self.name)
 | 
						|
 | 
						|
    @classmethod
 | 
						|
    def has_cfn_attr(cls, attr):
 | 
						|
        return attr in ["Arn", "ArchiveName"]
 | 
						|
 | 
						|
    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, account_id, region_name, **kwargs
 | 
						|
    ):
 | 
						|
        properties = cloudformation_json["Properties"]
 | 
						|
        event_backend = events_backends[account_id][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,
 | 
						|
        account_id,
 | 
						|
        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(account_id, region_name)
 | 
						|
            return cls.create_from_cloudformation_json(
 | 
						|
                new_resource_name, cloudformation_json, account_id, region_name
 | 
						|
            )
 | 
						|
 | 
						|
 | 
						|
@unique
 | 
						|
class ReplayState(Enum):
 | 
						|
    # https://docs.aws.amazon.com/eventbridge/latest/APIReference/API_ListReplays.html#API_ListReplays_RequestParameters
 | 
						|
    STARTING = "STARTING"
 | 
						|
    RUNNING = "RUNNING"
 | 
						|
    CANCELLING = "CANCELLING"
 | 
						|
    COMPLETED = "COMPLETED"
 | 
						|
    CANCELLED = "CANCELLED"
 | 
						|
    FAILED = "FAILED"
 | 
						|
 | 
						|
 | 
						|
class Replay(BaseModel):
 | 
						|
    def __init__(
 | 
						|
        self,
 | 
						|
        account_id,
 | 
						|
        region_name,
 | 
						|
        name,
 | 
						|
        description,
 | 
						|
        source_arn,
 | 
						|
        start_time,
 | 
						|
        end_time,
 | 
						|
        destination,
 | 
						|
    ):
 | 
						|
        self.account_id = account_id
 | 
						|
        self.region = region_name
 | 
						|
        self.name = name
 | 
						|
        self.description = description
 | 
						|
        self.source_arn = source_arn
 | 
						|
        self.event_start_time = start_time
 | 
						|
        self.event_end_time = end_time
 | 
						|
        self.destination = destination
 | 
						|
 | 
						|
        self.arn = f"arn:aws:events:{region_name}:{account_id}:replay/{name}"
 | 
						|
        self.state = ReplayState.STARTING
 | 
						|
        self.start_time = unix_time(datetime.utcnow())
 | 
						|
        self.end_time = None
 | 
						|
 | 
						|
    def describe_short(self):
 | 
						|
        return {
 | 
						|
            "ReplayName": self.name,
 | 
						|
            "EventSourceArn": self.source_arn,
 | 
						|
            "State": self.state.value,
 | 
						|
            "EventStartTime": self.event_start_time,
 | 
						|
            "EventEndTime": self.event_end_time,
 | 
						|
            "ReplayStartTime": self.start_time,
 | 
						|
            "ReplayEndTime": self.end_time,
 | 
						|
        }
 | 
						|
 | 
						|
    def describe(self):
 | 
						|
        result = {
 | 
						|
            "ReplayArn": self.arn,
 | 
						|
            "Description": self.description,
 | 
						|
            "Destination": self.destination,
 | 
						|
        }
 | 
						|
 | 
						|
        result.update(self.describe_short())
 | 
						|
 | 
						|
        return result
 | 
						|
 | 
						|
    def replay_events(self, archive):
 | 
						|
        event_bus_name = self.destination["Arn"].split("/")[-1]
 | 
						|
 | 
						|
        for event in archive.events:
 | 
						|
            event_backend = events_backends[self.account_id][self.region]
 | 
						|
            for rule in event_backend.rules.values():
 | 
						|
                rule.send_to_targets(
 | 
						|
                    event_bus_name,
 | 
						|
                    dict(
 | 
						|
                        event, **{"id": str(random.uuid4()), "replay-name": self.name}
 | 
						|
                    ),
 | 
						|
                )
 | 
						|
 | 
						|
        self.state = ReplayState.COMPLETED
 | 
						|
        self.end_time = unix_time(datetime.utcnow())
 | 
						|
 | 
						|
 | 
						|
class Connection(BaseModel):
 | 
						|
    def __init__(
 | 
						|
        self,
 | 
						|
        name,
 | 
						|
        account_id,
 | 
						|
        region_name,
 | 
						|
        description,
 | 
						|
        authorization_type,
 | 
						|
        auth_parameters,
 | 
						|
    ):
 | 
						|
        self.uuid = random.uuid4()
 | 
						|
        self.name = name
 | 
						|
        self.region = region_name
 | 
						|
        self.description = description
 | 
						|
        self.authorization_type = authorization_type
 | 
						|
        self.auth_parameters = auth_parameters
 | 
						|
        self.creation_time = unix_time(datetime.utcnow())
 | 
						|
        self.state = "AUTHORIZED"
 | 
						|
 | 
						|
        self.arn = f"arn:aws:events:{region_name}:{account_id}:connection/{self.name}/{self.uuid}"
 | 
						|
 | 
						|
    def describe_short(self):
 | 
						|
        """
 | 
						|
        Create the short description for the Connection object.
 | 
						|
 | 
						|
        Taken our from the Response Syntax of this API doc:
 | 
						|
            - https://docs.aws.amazon.com/eventbridge/latest/APIReference/API_DeleteConnection.html
 | 
						|
 | 
						|
        Something to consider:
 | 
						|
            - The original response also has
 | 
						|
                - LastAuthorizedTime (number)
 | 
						|
                - LastModifiedTime (number)
 | 
						|
            - At the time of implementing this, there was no place where to set/get
 | 
						|
            those attributes. That is why they are not in the response.
 | 
						|
 | 
						|
        Returns:
 | 
						|
            dict
 | 
						|
        """
 | 
						|
        return {
 | 
						|
            "ConnectionArn": self.arn,
 | 
						|
            "ConnectionState": self.state,
 | 
						|
            "CreationTime": self.creation_time,
 | 
						|
        }
 | 
						|
 | 
						|
    def describe(self):
 | 
						|
        """
 | 
						|
        Create a complete description for the Connection object.
 | 
						|
 | 
						|
        Taken our from the Response Syntax of this API doc:
 | 
						|
            - https://docs.aws.amazon.com/eventbridge/latest/APIReference/API_DescribeConnection.html
 | 
						|
 | 
						|
        Something to consider:
 | 
						|
            - The original response also has:
 | 
						|
                - LastAuthorizedTime (number)
 | 
						|
                - LastModifiedTime (number)
 | 
						|
                - SecretArn (string)
 | 
						|
                - StateReason (string)
 | 
						|
            - At the time of implementing this, there was no place where to set/get
 | 
						|
            those attributes. That is why they are not in the response.
 | 
						|
 | 
						|
        Returns:
 | 
						|
            dict
 | 
						|
        """
 | 
						|
        return {
 | 
						|
            "AuthorizationType": self.authorization_type,
 | 
						|
            "AuthParameters": self.auth_parameters,
 | 
						|
            "ConnectionArn": self.arn,
 | 
						|
            "ConnectionState": self.state,
 | 
						|
            "CreationTime": self.creation_time,
 | 
						|
            "Description": self.description,
 | 
						|
            "Name": self.name,
 | 
						|
        }
 | 
						|
 | 
						|
 | 
						|
class Destination(BaseModel):
 | 
						|
    def __init__(
 | 
						|
        self,
 | 
						|
        name,
 | 
						|
        account_id,
 | 
						|
        region_name,
 | 
						|
        description,
 | 
						|
        connection_arn,
 | 
						|
        invocation_endpoint,
 | 
						|
        invocation_rate_limit_per_second,
 | 
						|
        http_method,
 | 
						|
    ):
 | 
						|
        self.uuid = random.uuid4()
 | 
						|
        self.name = name
 | 
						|
        self.region = region_name
 | 
						|
        self.description = description
 | 
						|
        self.connection_arn = connection_arn
 | 
						|
        self.invocation_endpoint = invocation_endpoint
 | 
						|
        self.invocation_rate_limit_per_second = invocation_rate_limit_per_second
 | 
						|
        self.creation_time = unix_time(datetime.utcnow())
 | 
						|
        self.http_method = http_method
 | 
						|
        self.state = "ACTIVE"
 | 
						|
        self.arn = f"arn:aws:events:{region_name}:{account_id}:api-destination/{name}/{self.uuid}"
 | 
						|
 | 
						|
    def describe(self):
 | 
						|
        """
 | 
						|
        Describes the Destination object as a dict
 | 
						|
 | 
						|
        Docs:
 | 
						|
            Response Syntax in
 | 
						|
            https://docs.aws.amazon.com/eventbridge/latest/APIReference/API_DescribeApiDestination.html
 | 
						|
 | 
						|
        Something to consider:
 | 
						|
            - The response also has [InvocationRateLimitPerSecond] which was not
 | 
						|
            available when implementing this method
 | 
						|
 | 
						|
        Returns:
 | 
						|
            dict
 | 
						|
        """
 | 
						|
        return {
 | 
						|
            "ApiDestinationArn": self.arn,
 | 
						|
            "ApiDestinationState": self.state,
 | 
						|
            "ConnectionArn": self.connection_arn,
 | 
						|
            "CreationTime": self.creation_time,
 | 
						|
            "Description": self.description,
 | 
						|
            "HttpMethod": self.http_method,
 | 
						|
            "InvocationEndpoint": self.invocation_endpoint,
 | 
						|
            "InvocationRateLimitPerSecond": self.invocation_rate_limit_per_second,
 | 
						|
            "LastModifiedTime": self.creation_time,
 | 
						|
            "Name": self.name,
 | 
						|
        }
 | 
						|
 | 
						|
    def describe_short(self):
 | 
						|
        return {
 | 
						|
            "ApiDestinationArn": self.arn,
 | 
						|
            "ApiDestinationState": self.state,
 | 
						|
            "CreationTime": self.creation_time,
 | 
						|
            "LastModifiedTime": self.creation_time,
 | 
						|
        }
 | 
						|
 | 
						|
 | 
						|
class EventPattern:
 | 
						|
    def __init__(self, raw_pattern, pattern):
 | 
						|
        self._raw_pattern = raw_pattern
 | 
						|
        self._pattern = pattern
 | 
						|
 | 
						|
    def get_pattern(self):
 | 
						|
        return self._pattern
 | 
						|
 | 
						|
    def matches_event(self, event):
 | 
						|
        if not self._pattern:
 | 
						|
            return True
 | 
						|
        event = json.loads(json.dumps(event))
 | 
						|
        return self._does_event_match(event, self._pattern)
 | 
						|
 | 
						|
    def _does_event_match(self, event, pattern):
 | 
						|
        items_and_filters = [(event.get(k, UNDEFINED), v) for k, v in pattern.items()]
 | 
						|
        nested_filter_matches = [
 | 
						|
            self._does_event_match(item, nested_filter)
 | 
						|
            for item, nested_filter in items_and_filters
 | 
						|
            if isinstance(nested_filter, dict)
 | 
						|
        ]
 | 
						|
        filter_list_matches = [
 | 
						|
            self._does_item_match_filters(item, filter_list)
 | 
						|
            for item, filter_list in items_and_filters
 | 
						|
            if isinstance(filter_list, list)
 | 
						|
        ]
 | 
						|
        return all(nested_filter_matches + filter_list_matches)
 | 
						|
 | 
						|
    def _does_item_match_filters(self, item, filters):
 | 
						|
        allowed_values = [value for value in filters if isinstance(value, str)]
 | 
						|
        allowed_values_match = item in allowed_values if allowed_values else True
 | 
						|
        full_match = isinstance(item, list) and item == allowed_values
 | 
						|
        named_filter_matches = [
 | 
						|
            self._does_item_match_named_filter(item, pattern)
 | 
						|
            for pattern in filters
 | 
						|
            if isinstance(pattern, dict)
 | 
						|
        ]
 | 
						|
        return (full_match or allowed_values_match) and all(named_filter_matches)
 | 
						|
 | 
						|
    @staticmethod
 | 
						|
    def _does_item_match_named_filter(item, pattern):
 | 
						|
        filter_name, filter_value = list(pattern.items())[0]
 | 
						|
        if filter_name == "exists":
 | 
						|
            is_leaf_node = not isinstance(item, dict)
 | 
						|
            leaf_exists = is_leaf_node and item is not UNDEFINED
 | 
						|
            should_exist = filter_value
 | 
						|
            return leaf_exists if should_exist else not leaf_exists
 | 
						|
        if filter_name == "prefix":
 | 
						|
            prefix = filter_value
 | 
						|
            return item.startswith(prefix)
 | 
						|
        if filter_name == "numeric":
 | 
						|
            as_function = {"<": lt, "<=": le, "=": eq, ">=": ge, ">": gt}
 | 
						|
            operators_and_values = zip(filter_value[::2], filter_value[1::2])
 | 
						|
            numeric_matches = [
 | 
						|
                as_function[operator](item, value)
 | 
						|
                for operator, value in operators_and_values
 | 
						|
            ]
 | 
						|
            return all(numeric_matches)
 | 
						|
        else:
 | 
						|
            warnings.warn(
 | 
						|
                "'{}' filter logic unimplemented. defaulting to True".format(
 | 
						|
                    filter_name
 | 
						|
                )
 | 
						|
            )
 | 
						|
            return True
 | 
						|
 | 
						|
    @classmethod
 | 
						|
    def load(cls, raw_pattern):
 | 
						|
        parser = EventPatternParser(raw_pattern)
 | 
						|
        pattern = parser.parse()
 | 
						|
        return cls(raw_pattern, pattern)
 | 
						|
 | 
						|
    def dump(self):
 | 
						|
        return self._raw_pattern
 | 
						|
 | 
						|
 | 
						|
class EventPatternParser:
 | 
						|
    def __init__(self, pattern):
 | 
						|
        self.pattern = pattern
 | 
						|
 | 
						|
    def _validate_event_pattern(self, pattern):
 | 
						|
        # values in the event pattern have to be either a dict or an array
 | 
						|
        for attr, value in pattern.items():
 | 
						|
            if isinstance(value, dict):
 | 
						|
                self._validate_event_pattern(value)
 | 
						|
            elif isinstance(value, list):
 | 
						|
                if len(value) == 0:
 | 
						|
                    raise InvalidEventPatternException(
 | 
						|
                        reason="Empty arrays are not allowed"
 | 
						|
                    )
 | 
						|
            else:
 | 
						|
                raise InvalidEventPatternException(
 | 
						|
                    reason=f"'{attr}' must be an object or an array"
 | 
						|
                )
 | 
						|
 | 
						|
    def parse(self):
 | 
						|
        try:
 | 
						|
            parsed_pattern = json.loads(self.pattern) if self.pattern else dict()
 | 
						|
            self._validate_event_pattern(parsed_pattern)
 | 
						|
            return parsed_pattern
 | 
						|
        except JSONDecodeError:
 | 
						|
            raise InvalidEventPatternException(reason="Invalid JSON")
 | 
						|
 | 
						|
 | 
						|
class EventsBackend(BaseBackend):
 | 
						|
    """
 | 
						|
    When a event occurs, the appropriate targets are triggered for a subset of usecases.
 | 
						|
 | 
						|
    Supported events: S3:CreateBucket
 | 
						|
 | 
						|
    Supported targets: AWSLambda functions
 | 
						|
    """
 | 
						|
 | 
						|
    ACCOUNT_ID = re.compile(r"^(\d{1,12}|\*)$")
 | 
						|
    STATEMENT_ID = re.compile(r"^[a-zA-Z0-9-_]{1,64}$")
 | 
						|
    _CRON_REGEX = re.compile(r"^cron\(.*\)")
 | 
						|
    _RATE_REGEX = re.compile(r"^rate\(\d*\s(minute|minutes|hour|hours|day|days)\)")
 | 
						|
 | 
						|
    def __init__(self, region_name, account_id):
 | 
						|
        super().__init__(region_name, account_id)
 | 
						|
        self.rules = OrderedDict()
 | 
						|
        self.next_tokens = {}
 | 
						|
        self.event_buses = {}
 | 
						|
        self.event_sources = {}
 | 
						|
        self.archives = {}
 | 
						|
        self.replays = {}
 | 
						|
        self.tagger = TaggingService()
 | 
						|
 | 
						|
        self._add_default_event_bus()
 | 
						|
        self.connections = {}
 | 
						|
        self.destinations = {}
 | 
						|
 | 
						|
    @staticmethod
 | 
						|
    def default_vpc_endpoint_service(service_region, zones):
 | 
						|
        """Default VPC endpoint service."""
 | 
						|
        return BaseBackend.default_vpc_endpoint_service_factory(
 | 
						|
            service_region, zones, "events"
 | 
						|
        )
 | 
						|
 | 
						|
    def _add_default_event_bus(self):
 | 
						|
        self.event_buses["default"] = EventBus(
 | 
						|
            self.account_id, self.region_name, "default"
 | 
						|
        )
 | 
						|
 | 
						|
    def _gen_next_token(self, index):
 | 
						|
        token = os.urandom(128).encode("base64")
 | 
						|
        self.next_tokens[token] = index
 | 
						|
        return token
 | 
						|
 | 
						|
    def _process_token_and_limits(self, array_len, next_token=None, limit=None):
 | 
						|
        start_index = 0
 | 
						|
        end_index = array_len
 | 
						|
        new_next_token = None
 | 
						|
 | 
						|
        if next_token:
 | 
						|
            start_index = self.next_tokens.pop(next_token, 0)
 | 
						|
 | 
						|
        if limit is not None:
 | 
						|
            new_end_index = start_index + int(limit)
 | 
						|
            if new_end_index < end_index:
 | 
						|
                end_index = new_end_index
 | 
						|
                new_next_token = self._gen_next_token(end_index)
 | 
						|
 | 
						|
        return start_index, end_index, new_next_token
 | 
						|
 | 
						|
    def _get_event_bus(self, name):
 | 
						|
        event_bus_name = name.split("/")[-1]
 | 
						|
 | 
						|
        event_bus = self.event_buses.get(event_bus_name)
 | 
						|
        if not event_bus:
 | 
						|
            raise ResourceNotFoundException(
 | 
						|
                "Event bus {} does not exist.".format(event_bus_name)
 | 
						|
            )
 | 
						|
 | 
						|
        return event_bus
 | 
						|
 | 
						|
    def _get_replay(self, name):
 | 
						|
        replay = self.replays.get(name)
 | 
						|
        if not replay:
 | 
						|
            raise ResourceNotFoundException("Replay {} does not exist.".format(name))
 | 
						|
 | 
						|
        return replay
 | 
						|
 | 
						|
    def put_rule(
 | 
						|
        self,
 | 
						|
        name,
 | 
						|
        *,
 | 
						|
        description=None,
 | 
						|
        event_bus_name=None,
 | 
						|
        event_pattern=None,
 | 
						|
        role_arn=None,
 | 
						|
        scheduled_expression=None,
 | 
						|
        state=None,
 | 
						|
        managed_by=None,
 | 
						|
        tags=None,
 | 
						|
    ):
 | 
						|
        event_bus_name = event_bus_name or "default"
 | 
						|
 | 
						|
        if not event_pattern and not scheduled_expression:
 | 
						|
            raise JsonRESTError(
 | 
						|
                "ValidationException",
 | 
						|
                "Parameter(s) EventPattern or ScheduleExpression must be specified.",
 | 
						|
            )
 | 
						|
 | 
						|
        if scheduled_expression:
 | 
						|
            if event_bus_name != "default":
 | 
						|
                raise ValidationException(
 | 
						|
                    "ScheduleExpression is supported only on the default event bus."
 | 
						|
                )
 | 
						|
 | 
						|
            if not (
 | 
						|
                self._CRON_REGEX.match(scheduled_expression)
 | 
						|
                or self._RATE_REGEX.match(scheduled_expression)
 | 
						|
            ):
 | 
						|
                raise ValidationException("Parameter ScheduleExpression is not valid.")
 | 
						|
 | 
						|
        existing_rule = self.rules.get(name)
 | 
						|
        targets = existing_rule.targets if existing_rule else list()
 | 
						|
        rule = Rule(
 | 
						|
            name,
 | 
						|
            self.account_id,
 | 
						|
            self.region_name,
 | 
						|
            description,
 | 
						|
            event_pattern,
 | 
						|
            scheduled_expression,
 | 
						|
            role_arn,
 | 
						|
            event_bus_name,
 | 
						|
            state,
 | 
						|
            managed_by,
 | 
						|
            targets=targets,
 | 
						|
        )
 | 
						|
        self.rules[name] = rule
 | 
						|
 | 
						|
        if tags:
 | 
						|
            self.tagger.tag_resource(rule.arn, tags)
 | 
						|
 | 
						|
        return rule
 | 
						|
 | 
						|
    def delete_rule(self, name):
 | 
						|
        rule = self.rules.get(name)
 | 
						|
        if len(rule.targets) > 0:
 | 
						|
            raise ValidationException("Rule can't be deleted since it has targets.")
 | 
						|
 | 
						|
        arn = rule.arn
 | 
						|
        if self.tagger.has_tags(arn):
 | 
						|
            self.tagger.delete_all_tags_for_resource(arn)
 | 
						|
        return self.rules.pop(name) is not None
 | 
						|
 | 
						|
    def describe_rule(self, name):
 | 
						|
        rule = self.rules.get(name)
 | 
						|
        if not rule:
 | 
						|
            raise ResourceNotFoundException("Rule {} does not exist.".format(name))
 | 
						|
        return rule
 | 
						|
 | 
						|
    def disable_rule(self, name):
 | 
						|
        if name in self.rules:
 | 
						|
            self.rules[name].disable()
 | 
						|
            return True
 | 
						|
 | 
						|
        return False
 | 
						|
 | 
						|
    def enable_rule(self, name):
 | 
						|
        if name in self.rules:
 | 
						|
            self.rules[name].enable()
 | 
						|
            return True
 | 
						|
 | 
						|
        return False
 | 
						|
 | 
						|
    @paginate(pagination_model=PAGINATION_MODEL)
 | 
						|
    def list_rule_names_by_target(self, target_arn):
 | 
						|
        matching_rules = []
 | 
						|
 | 
						|
        for _, rule in self.rules.items():
 | 
						|
            for target in rule.targets:
 | 
						|
                if target["Arn"] == target_arn:
 | 
						|
                    matching_rules.append(rule)
 | 
						|
 | 
						|
        return matching_rules
 | 
						|
 | 
						|
    @paginate(pagination_model=PAGINATION_MODEL)
 | 
						|
    def list_rules(self, prefix=None):
 | 
						|
        match_string = ".*"
 | 
						|
        if prefix is not None:
 | 
						|
            match_string = "^" + prefix + match_string
 | 
						|
 | 
						|
        match_regex = re.compile(match_string)
 | 
						|
 | 
						|
        matching_rules = []
 | 
						|
 | 
						|
        for name, rule in self.rules.items():
 | 
						|
            if match_regex.match(name):
 | 
						|
                matching_rules.append(rule)
 | 
						|
 | 
						|
        return matching_rules
 | 
						|
 | 
						|
    def list_targets_by_rule(self, rule, next_token=None, limit=None):
 | 
						|
        # We'll let a KeyError exception be thrown for response to handle if
 | 
						|
        # rule doesn't exist.
 | 
						|
        rule = self.rules[rule]
 | 
						|
 | 
						|
        start_index, end_index, new_next_token = self._process_token_and_limits(
 | 
						|
            len(rule.targets), next_token, limit
 | 
						|
        )
 | 
						|
 | 
						|
        returned_targets = []
 | 
						|
        return_obj = {}
 | 
						|
 | 
						|
        for i in range(start_index, end_index):
 | 
						|
            returned_targets.append(rule.targets[i])
 | 
						|
 | 
						|
        return_obj["Targets"] = returned_targets
 | 
						|
        if new_next_token is not None:
 | 
						|
            return_obj["NextToken"] = new_next_token
 | 
						|
 | 
						|
        return return_obj
 | 
						|
 | 
						|
    def put_targets(self, name, event_bus_name, targets):
 | 
						|
        # super simple ARN check
 | 
						|
        invalid_arn = next(
 | 
						|
            (
 | 
						|
                target["Arn"]
 | 
						|
                for target in targets
 | 
						|
                if not re.match(r"arn:[\d\w:\-/]*", target["Arn"])
 | 
						|
            ),
 | 
						|
            None,
 | 
						|
        )
 | 
						|
        if invalid_arn:
 | 
						|
            raise ValidationException(
 | 
						|
                "Parameter {} is not valid. "
 | 
						|
                "Reason: Provided Arn is not in correct format.".format(invalid_arn)
 | 
						|
            )
 | 
						|
 | 
						|
        for target in targets:
 | 
						|
            arn = target["Arn"]
 | 
						|
 | 
						|
            if (
 | 
						|
                ":sqs:" in arn
 | 
						|
                and arn.endswith(".fifo")
 | 
						|
                and not target.get("SqsParameters")
 | 
						|
            ):
 | 
						|
                raise ValidationException(
 | 
						|
                    "Parameter(s) SqsParameters must be specified for target: {}.".format(
 | 
						|
                        target["Id"]
 | 
						|
                    )
 | 
						|
                )
 | 
						|
 | 
						|
        rule = self.rules.get(name)
 | 
						|
 | 
						|
        if not rule:
 | 
						|
            raise ResourceNotFoundException(
 | 
						|
                "Rule {0} does not exist on EventBus {1}.".format(name, event_bus_name)
 | 
						|
            )
 | 
						|
 | 
						|
        rule.put_targets(targets)
 | 
						|
 | 
						|
    def put_events(self, events):
 | 
						|
        num_events = len(events)
 | 
						|
 | 
						|
        if num_events > 10:
 | 
						|
            # the exact error text is longer, the Value list consists of all the put events
 | 
						|
            raise ValidationException(
 | 
						|
                "1 validation error detected: "
 | 
						|
                "Value '[PutEventsRequestEntry]' at 'entries' failed to satisfy constraint: "
 | 
						|
                "Member must have length less than or equal to 10"
 | 
						|
            )
 | 
						|
 | 
						|
        entries = []
 | 
						|
        for event in events:
 | 
						|
            if "Source" not in event:
 | 
						|
                entries.append(
 | 
						|
                    {
 | 
						|
                        "ErrorCode": "InvalidArgument",
 | 
						|
                        "ErrorMessage": "Parameter Source is not valid. Reason: Source is a required argument.",
 | 
						|
                    }
 | 
						|
                )
 | 
						|
            elif "DetailType" not in event:
 | 
						|
                entries.append(
 | 
						|
                    {
 | 
						|
                        "ErrorCode": "InvalidArgument",
 | 
						|
                        "ErrorMessage": "Parameter DetailType is not valid. Reason: DetailType is a required argument.",
 | 
						|
                    }
 | 
						|
                )
 | 
						|
            elif "Detail" not in event:
 | 
						|
                entries.append(
 | 
						|
                    {
 | 
						|
                        "ErrorCode": "InvalidArgument",
 | 
						|
                        "ErrorMessage": "Parameter Detail is not valid. Reason: Detail is a required argument.",
 | 
						|
                    }
 | 
						|
                )
 | 
						|
            else:
 | 
						|
                try:
 | 
						|
                    json.loads(event["Detail"])
 | 
						|
                except ValueError:  # json.JSONDecodeError exists since Python 3.5
 | 
						|
                    entries.append(
 | 
						|
                        {
 | 
						|
                            "ErrorCode": "MalformedDetail",
 | 
						|
                            "ErrorMessage": "Detail is malformed.",
 | 
						|
                        }
 | 
						|
                    )
 | 
						|
                    continue
 | 
						|
 | 
						|
                event_id = str(random.uuid4())
 | 
						|
                entries.append({"EventId": event_id})
 | 
						|
 | 
						|
                # if 'EventBusName' is not especially set, it will be sent to the default one
 | 
						|
                event_bus_name = event.get("EventBusName", "default")
 | 
						|
 | 
						|
                for rule in self.rules.values():
 | 
						|
                    rule.send_to_targets(
 | 
						|
                        event_bus_name,
 | 
						|
                        {
 | 
						|
                            "version": "0",
 | 
						|
                            "id": event_id,
 | 
						|
                            "detail-type": event["DetailType"],
 | 
						|
                            "source": event["Source"],
 | 
						|
                            "account": self.account_id,
 | 
						|
                            "time": event.get("Time", unix_time(datetime.utcnow())),
 | 
						|
                            "region": self.region_name,
 | 
						|
                            "resources": event.get("Resources", []),
 | 
						|
                            "detail": json.loads(event["Detail"]),
 | 
						|
                        },
 | 
						|
                    )
 | 
						|
 | 
						|
        return entries
 | 
						|
 | 
						|
    def remove_targets(self, name, event_bus_name, ids):
 | 
						|
        rule = self.rules.get(name)
 | 
						|
 | 
						|
        if not rule:
 | 
						|
            raise ResourceNotFoundException(
 | 
						|
                "Rule {0} does not exist on EventBus {1}.".format(name, event_bus_name)
 | 
						|
            )
 | 
						|
 | 
						|
        rule.remove_targets(ids)
 | 
						|
 | 
						|
    def test_event_pattern(self):
 | 
						|
        raise NotImplementedError()
 | 
						|
 | 
						|
    @staticmethod
 | 
						|
    def _put_permission_from_policy(event_bus, policy):
 | 
						|
        try:
 | 
						|
            policy_doc = json.loads(policy)
 | 
						|
            event_bus.add_policy(policy_doc)
 | 
						|
        except JSONDecodeError:
 | 
						|
            raise JsonRESTError(
 | 
						|
                "ValidationException", "This policy contains invalid Json"
 | 
						|
            )
 | 
						|
 | 
						|
    @staticmethod
 | 
						|
    def _condition_param_to_stmt_condition(condition):
 | 
						|
        if condition:
 | 
						|
            key = condition["Key"]
 | 
						|
            value = condition["Value"]
 | 
						|
            condition_type = condition["Type"]
 | 
						|
            return {condition_type: {key: value}}
 | 
						|
        return None
 | 
						|
 | 
						|
    def _put_permission_from_params(
 | 
						|
        self, event_bus, action, principal, statement_id, condition
 | 
						|
    ):
 | 
						|
        if principal is None:
 | 
						|
            raise JsonRESTError(
 | 
						|
                "ValidationException", "Parameter Principal must be specified."
 | 
						|
            )
 | 
						|
 | 
						|
        if condition and principal != "*":
 | 
						|
            raise JsonRESTError(
 | 
						|
                "InvalidParameterValue",
 | 
						|
                "Value of the parameter 'principal' must be '*' when the parameter 'condition' is set.",
 | 
						|
            )
 | 
						|
 | 
						|
        if not condition and self.ACCOUNT_ID.match(principal) is None:
 | 
						|
            raise JsonRESTError(
 | 
						|
                "InvalidParameterValue",
 | 
						|
                f"Value {principal} at 'principal' failed to satisfy constraint: "
 | 
						|
                r"Member must satisfy regular expression pattern: (\d{12}|\*)",
 | 
						|
            )
 | 
						|
 | 
						|
        if action is None or action != "events:PutEvents":
 | 
						|
            raise JsonRESTError(
 | 
						|
                "ValidationException",
 | 
						|
                "Provided value in parameter 'action' is not supported.",
 | 
						|
            )
 | 
						|
 | 
						|
        if statement_id is None or self.STATEMENT_ID.match(statement_id) is None:
 | 
						|
            raise JsonRESTError(
 | 
						|
                "InvalidParameterValue", r"StatementId must match ^[a-zA-Z0-9-_]{1,64}$"
 | 
						|
            )
 | 
						|
 | 
						|
        principal = {"AWS": f"arn:aws:iam::{principal}:root"}
 | 
						|
        stmt_condition = self._condition_param_to_stmt_condition(condition)
 | 
						|
        event_bus.add_permission(statement_id, action, principal, stmt_condition)
 | 
						|
 | 
						|
    def put_permission(
 | 
						|
        self, event_bus_name, action, principal, statement_id, condition, policy
 | 
						|
    ):
 | 
						|
        if not event_bus_name:
 | 
						|
            event_bus_name = "default"
 | 
						|
 | 
						|
        event_bus = self.describe_event_bus(event_bus_name)
 | 
						|
 | 
						|
        if policy:
 | 
						|
            self._put_permission_from_policy(event_bus, policy)
 | 
						|
        else:
 | 
						|
            self._put_permission_from_params(
 | 
						|
                event_bus, action, principal, statement_id, condition
 | 
						|
            )
 | 
						|
 | 
						|
    def remove_permission(self, event_bus_name, statement_id, remove_all_permissions):
 | 
						|
        if not event_bus_name:
 | 
						|
            event_bus_name = "default"
 | 
						|
 | 
						|
        event_bus = self.describe_event_bus(event_bus_name)
 | 
						|
 | 
						|
        if remove_all_permissions:
 | 
						|
            event_bus.remove_statements()
 | 
						|
        else:
 | 
						|
            if not event_bus.has_permissions():
 | 
						|
                raise JsonRESTError(
 | 
						|
                    "ResourceNotFoundException", "EventBus does not have a policy."
 | 
						|
                )
 | 
						|
 | 
						|
            statement = event_bus.remove_statement(statement_id)
 | 
						|
            if not statement:
 | 
						|
                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._get_event_bus(name)
 | 
						|
 | 
						|
        return event_bus
 | 
						|
 | 
						|
    def create_event_bus(self, name, event_source_name=None, tags=None):
 | 
						|
        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),
 | 
						|
            )
 | 
						|
 | 
						|
        event_bus = EventBus(self.account_id, self.region_name, name, tags=tags)
 | 
						|
        self.event_buses[name] = event_bus
 | 
						|
        if tags:
 | 
						|
            self.tagger.tag_resource(event_bus.arn, tags)
 | 
						|
 | 
						|
        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."
 | 
						|
            )
 | 
						|
        event_bus = self.event_buses.pop(name, None)
 | 
						|
        if event_bus:
 | 
						|
            self.tagger.delete_all_tags_for_resource(event_bus.arn)
 | 
						|
 | 
						|
    def list_tags_for_resource(self, arn):
 | 
						|
        name = arn.split("/")[-1]
 | 
						|
        registries = [self.rules, self.event_buses]
 | 
						|
        for registry in registries:
 | 
						|
            if name in registry:
 | 
						|
                return self.tagger.list_tags_for_resource(registry[name].arn)
 | 
						|
        raise ResourceNotFoundException(
 | 
						|
            "Rule {0} does not exist on EventBus default.".format(name)
 | 
						|
        )
 | 
						|
 | 
						|
    def tag_resource(self, arn, tags):
 | 
						|
        name = arn.split("/")[-1]
 | 
						|
        registries = [self.rules, self.event_buses]
 | 
						|
        for registry in registries:
 | 
						|
            if name in registry:
 | 
						|
                self.tagger.tag_resource(registry[name].arn, tags)
 | 
						|
                return {}
 | 
						|
        raise ResourceNotFoundException(
 | 
						|
            "Rule {0} does not exist on EventBus default.".format(name)
 | 
						|
        )
 | 
						|
 | 
						|
    def untag_resource(self, arn, tag_names):
 | 
						|
        name = arn.split("/")[-1]
 | 
						|
        registries = [self.rules, self.event_buses]
 | 
						|
        for registry in registries:
 | 
						|
            if name in registry:
 | 
						|
                self.tagger.untag_resource_using_names(registry[name].arn, tag_names)
 | 
						|
                return {}
 | 
						|
        raise ResourceNotFoundException(
 | 
						|
            "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)
 | 
						|
            )
 | 
						|
 | 
						|
        event_bus = self._get_event_bus(source_arn)
 | 
						|
 | 
						|
        if name in self.archives:
 | 
						|
            raise ResourceAlreadyExistsException(
 | 
						|
                "Archive {} already exists.".format(name)
 | 
						|
            )
 | 
						|
 | 
						|
        archive = Archive(
 | 
						|
            self.account_id,
 | 
						|
            self.region_name,
 | 
						|
            name,
 | 
						|
            source_arn,
 | 
						|
            description,
 | 
						|
            event_pattern,
 | 
						|
            retention,
 | 
						|
        )
 | 
						|
 | 
						|
        rule_event_pattern = json.loads(event_pattern or "{}")
 | 
						|
        rule_event_pattern["replay-name"] = [{"exists": False}]
 | 
						|
 | 
						|
        rule_name = "Events-Archive-{}".format(name)
 | 
						|
        rule = self.put_rule(
 | 
						|
            rule_name,
 | 
						|
            event_pattern=json.dumps(rule_event_pattern),
 | 
						|
            event_bus_name=event_bus.name,
 | 
						|
            managed_by="prod.vhs.events.aws.internal",
 | 
						|
        )
 | 
						|
        self.put_targets(
 | 
						|
            rule.name,
 | 
						|
            rule.event_bus_name,
 | 
						|
            [
 | 
						|
                {
 | 
						|
                    "Id": rule.name,
 | 
						|
                    "Arn": "arn:aws:events:{}:::".format(self.region_name),
 | 
						|
                    "InputTransformer": {
 | 
						|
                        "InputPathsMap": {},
 | 
						|
                        "InputTemplate": json.dumps(
 | 
						|
                            {
 | 
						|
                                "archive-arn": "{0}:{1}".format(
 | 
						|
                                    archive.arn, archive.uuid
 | 
						|
                                ),
 | 
						|
                                "event": "<aws.events.event.json>",
 | 
						|
                                "ingestion-time": "<aws.events.event.ingestion-time>",
 | 
						|
                            }
 | 
						|
                        ),
 | 
						|
                    },
 | 
						|
                }
 | 
						|
            ],
 | 
						|
        )
 | 
						|
 | 
						|
        self.archives[name] = archive
 | 
						|
 | 
						|
        return archive
 | 
						|
 | 
						|
    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))
 | 
						|
 | 
						|
        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.account_id, self.region_name)
 | 
						|
 | 
						|
    def start_replay(
 | 
						|
        self, name, description, source_arn, start_time, end_time, destination
 | 
						|
    ):
 | 
						|
        event_bus_arn = destination["Arn"]
 | 
						|
        event_bus_arn_pattern = r"^arn:aws:events:[a-zA-Z0-9-]+:\d{12}:event-bus/"
 | 
						|
        if not re.match(event_bus_arn_pattern, event_bus_arn):
 | 
						|
            raise ValidationException(
 | 
						|
                "Parameter Destination.Arn is not valid. "
 | 
						|
                "Reason: Must contain an event bus ARN."
 | 
						|
            )
 | 
						|
 | 
						|
        self._get_event_bus(event_bus_arn)
 | 
						|
 | 
						|
        archive_name = source_arn.split("/")[-1]
 | 
						|
        archive = self.archives.get(archive_name)
 | 
						|
        if not archive:
 | 
						|
            raise ValidationException(
 | 
						|
                "Parameter EventSourceArn is not valid. "
 | 
						|
                "Reason: Archive {} does not exist.".format(archive_name)
 | 
						|
            )
 | 
						|
 | 
						|
        if event_bus_arn != archive.source_arn:
 | 
						|
            raise ValidationException(
 | 
						|
                "Parameter Destination.Arn is not valid. "
 | 
						|
                "Reason: Cross event bus replay is not permitted."
 | 
						|
            )
 | 
						|
 | 
						|
        if start_time > end_time:
 | 
						|
            raise ValidationException(
 | 
						|
                "Parameter EventEndTime is not valid. "
 | 
						|
                "Reason: EventStartTime must be before EventEndTime."
 | 
						|
            )
 | 
						|
 | 
						|
        if name in self.replays:
 | 
						|
            raise ResourceAlreadyExistsException(
 | 
						|
                "Replay {} already exists.".format(name)
 | 
						|
            )
 | 
						|
 | 
						|
        replay = Replay(
 | 
						|
            self.account_id,
 | 
						|
            self.region_name,
 | 
						|
            name,
 | 
						|
            description,
 | 
						|
            source_arn,
 | 
						|
            start_time,
 | 
						|
            end_time,
 | 
						|
            destination,
 | 
						|
        )
 | 
						|
 | 
						|
        self.replays[name] = replay
 | 
						|
 | 
						|
        replay.replay_events(archive)
 | 
						|
 | 
						|
        return {
 | 
						|
            "ReplayArn": replay.arn,
 | 
						|
            "ReplayStartTime": replay.start_time,
 | 
						|
            "State": ReplayState.STARTING.value,  # the replay will be done before returning the response
 | 
						|
        }
 | 
						|
 | 
						|
    def describe_replay(self, name):
 | 
						|
        replay = self._get_replay(name)
 | 
						|
 | 
						|
        return replay.describe()
 | 
						|
 | 
						|
    def list_replays(self, name_prefix, source_arn, state):
 | 
						|
        if [name_prefix, source_arn, state].count(None) < 2:
 | 
						|
            raise ValidationException(
 | 
						|
                "At most one filter is allowed for ListReplays. "
 | 
						|
                "Use either : State, EventSourceArn, or NamePrefix."
 | 
						|
            )
 | 
						|
 | 
						|
        valid_states = sorted([item.value for item in ReplayState])
 | 
						|
        if state and state not in 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(valid_states))
 | 
						|
            )
 | 
						|
 | 
						|
        if [name_prefix, source_arn, state].count(None) == 3:
 | 
						|
            return [replay.describe_short() for replay in self.replays.values()]
 | 
						|
 | 
						|
        result = []
 | 
						|
 | 
						|
        for replay in self.replays.values():
 | 
						|
            if name_prefix and replay.name.startswith(name_prefix):
 | 
						|
                result.append(replay.describe_short())
 | 
						|
            elif source_arn and replay.source_arn == source_arn:
 | 
						|
                result.append(replay.describe_short())
 | 
						|
            elif state and replay.state == state:
 | 
						|
                result.append(replay.describe_short())
 | 
						|
 | 
						|
        return result
 | 
						|
 | 
						|
    def cancel_replay(self, name):
 | 
						|
        replay = self._get_replay(name)
 | 
						|
 | 
						|
        # replays in the state 'COMPLETED' can't be canceled,
 | 
						|
        # but the implementation is done synchronously,
 | 
						|
        # so they are done right after the start
 | 
						|
        if replay.state not in [
 | 
						|
            ReplayState.STARTING,
 | 
						|
            ReplayState.RUNNING,
 | 
						|
            ReplayState.COMPLETED,
 | 
						|
        ]:
 | 
						|
            raise IllegalStatusException(
 | 
						|
                "Replay {} is not in a valid state for this operation.".format(name)
 | 
						|
            )
 | 
						|
 | 
						|
        replay.state = ReplayState.CANCELLED
 | 
						|
 | 
						|
        return {"ReplayArn": replay.arn, "State": ReplayState.CANCELLING.value}
 | 
						|
 | 
						|
    def create_connection(self, name, description, authorization_type, auth_parameters):
 | 
						|
        connection = Connection(
 | 
						|
            name,
 | 
						|
            self.account_id,
 | 
						|
            self.region_name,
 | 
						|
            description,
 | 
						|
            authorization_type,
 | 
						|
            auth_parameters,
 | 
						|
        )
 | 
						|
        self.connections[name] = connection
 | 
						|
        return connection
 | 
						|
 | 
						|
    def update_connection(self, *, name, **kwargs):
 | 
						|
        connection = self.connections.get(name)
 | 
						|
        if not connection:
 | 
						|
            raise ResourceNotFoundException(
 | 
						|
                "Connection '{}' does not exist.".format(name)
 | 
						|
            )
 | 
						|
 | 
						|
        for attr, value in kwargs.items():
 | 
						|
            if value is not None and hasattr(connection, attr):
 | 
						|
                setattr(connection, attr, value)
 | 
						|
        return connection.describe_short()
 | 
						|
 | 
						|
    def list_connections(self):
 | 
						|
        return self.connections.values()
 | 
						|
 | 
						|
    def describe_connection(self, name):
 | 
						|
        """
 | 
						|
        Retrieves details about a connection.
 | 
						|
 | 
						|
        Docs:
 | 
						|
            https://docs.aws.amazon.com/eventbridge/latest/APIReference/API_DescribeConnection.html
 | 
						|
 | 
						|
        Args:
 | 
						|
            name: The name of the connection to retrieve.
 | 
						|
 | 
						|
        Raises:
 | 
						|
            ResourceNotFoundException: When the connection is not present.
 | 
						|
 | 
						|
        Returns:
 | 
						|
            dict
 | 
						|
        """
 | 
						|
        connection = self.connections.get(name)
 | 
						|
        if not connection:
 | 
						|
            raise ResourceNotFoundException(
 | 
						|
                "Connection '{}' does not exist.".format(name)
 | 
						|
            )
 | 
						|
 | 
						|
        return connection.describe()
 | 
						|
 | 
						|
    def delete_connection(self, name):
 | 
						|
        """
 | 
						|
        Deletes a connection.
 | 
						|
 | 
						|
        Docs:
 | 
						|
            https://docs.aws.amazon.com/eventbridge/latest/APIReference/API_DeleteConnection.html
 | 
						|
 | 
						|
        Args:
 | 
						|
            name: The name of the connection to delete.
 | 
						|
 | 
						|
        Raises:
 | 
						|
            ResourceNotFoundException: When the connection is not present.
 | 
						|
 | 
						|
        Returns:
 | 
						|
            dict
 | 
						|
        """
 | 
						|
        connection = self.connections.pop(name, None)
 | 
						|
        if not connection:
 | 
						|
            raise ResourceNotFoundException(
 | 
						|
                "Connection '{}' does not exist.".format(name)
 | 
						|
            )
 | 
						|
 | 
						|
        return connection.describe_short()
 | 
						|
 | 
						|
    def create_api_destination(
 | 
						|
        self,
 | 
						|
        name,
 | 
						|
        description,
 | 
						|
        connection_arn,
 | 
						|
        invocation_endpoint,
 | 
						|
        invocation_rate_limit_per_second,
 | 
						|
        http_method,
 | 
						|
    ):
 | 
						|
        """
 | 
						|
        Creates an API destination, which is an HTTP invocation endpoint configured as a target for events.
 | 
						|
 | 
						|
        Docs:
 | 
						|
            https://docs.aws.amazon.com/eventbridge/latest/APIReference/API_CreateApiDestination.html
 | 
						|
 | 
						|
        Returns:
 | 
						|
            dict
 | 
						|
        """
 | 
						|
        destination = Destination(
 | 
						|
            name=name,
 | 
						|
            account_id=self.account_id,
 | 
						|
            region_name=self.region_name,
 | 
						|
            description=description,
 | 
						|
            connection_arn=connection_arn,
 | 
						|
            invocation_endpoint=invocation_endpoint,
 | 
						|
            invocation_rate_limit_per_second=invocation_rate_limit_per_second,
 | 
						|
            http_method=http_method,
 | 
						|
        )
 | 
						|
 | 
						|
        self.destinations[name] = destination
 | 
						|
        return destination.describe_short()
 | 
						|
 | 
						|
    def list_api_destinations(self):
 | 
						|
        return self.destinations.values()
 | 
						|
 | 
						|
    def describe_api_destination(self, name):
 | 
						|
        """
 | 
						|
        Retrieves details about an API destination.
 | 
						|
 | 
						|
        Docs:
 | 
						|
            https://docs.aws.amazon.com/eventbridge/latest/APIReference/API_DescribeApiDestination.html
 | 
						|
        Args:
 | 
						|
            name: The name of the API destination to retrieve.
 | 
						|
 | 
						|
        Returns:
 | 
						|
            dict
 | 
						|
        """
 | 
						|
        destination = self.destinations.get(name)
 | 
						|
        if not destination:
 | 
						|
            raise ResourceNotFoundException(
 | 
						|
                "An api-destination '{}' does not exist.".format(name)
 | 
						|
            )
 | 
						|
        return destination.describe()
 | 
						|
 | 
						|
    def update_api_destination(self, *, name, **kwargs):
 | 
						|
        """
 | 
						|
        Creates an API destination, which is an HTTP invocation endpoint configured as a target for events.
 | 
						|
 | 
						|
        Docs:
 | 
						|
            https://docs.aws.amazon.com/eventbridge/latest/APIReference/API_UpdateApiDestination.html
 | 
						|
 | 
						|
        Returns:
 | 
						|
            dict
 | 
						|
        """
 | 
						|
        destination = self.destinations.get(name)
 | 
						|
        if not destination:
 | 
						|
            raise ResourceNotFoundException(
 | 
						|
                "An api-destination '{}' does not exist.".format(name)
 | 
						|
            )
 | 
						|
 | 
						|
        for attr, value in kwargs.items():
 | 
						|
            if value is not None and hasattr(destination, attr):
 | 
						|
                setattr(destination, attr, value)
 | 
						|
        return destination.describe_short()
 | 
						|
 | 
						|
    def delete_api_destination(self, name):
 | 
						|
        """
 | 
						|
        Deletes the specified API destination.
 | 
						|
 | 
						|
        Docs:
 | 
						|
            https://docs.aws.amazon.com/eventbridge/latest/APIReference/API_DeleteApiDestination.html
 | 
						|
 | 
						|
        Args:
 | 
						|
            name: The name of the destination to delete.
 | 
						|
 | 
						|
        Raises:
 | 
						|
            ResourceNotFoundException: When the destination is not present.
 | 
						|
 | 
						|
        Returns:
 | 
						|
            dict
 | 
						|
 | 
						|
        """
 | 
						|
        destination = self.destinations.pop(name, None)
 | 
						|
        if not destination:
 | 
						|
            raise ResourceNotFoundException(
 | 
						|
                "An api-destination '{}' does not exist.".format(name)
 | 
						|
            )
 | 
						|
        return {}
 | 
						|
 | 
						|
 | 
						|
events_backends = BackendDict(EventsBackend, "events")
 |