diff --git a/moto/events/models.py b/moto/events/models.py index 66a60bdf4..da75d4483 100644 --- a/moto/events/models.py +++ b/moto/events/models.py @@ -27,23 +27,34 @@ from uuid import uuid4 class Rule(CloudFormationModel): Arn = namedtuple("Arn", ["service", "resource_type", "resource_id"]) - def _generate_arn(self, name): - return "arn:aws:events:{region_name}:{account}:rule/{name}".format( - region_name=self.region_name, account=ACCOUNT_ID, name=name - ) - def __init__(self, name, region_name, **kwargs): self.name = name self.region_name = region_name - self.arn = kwargs.get("Arn") or self._generate_arn(name) self.event_pattern = kwargs.get("EventPattern") self.schedule_exp = kwargs.get("ScheduleExpression") self.state = kwargs.get("State") or "ENABLED" self.description = kwargs.get("Description") self.role_arn = kwargs.get("RoleArn") - self.event_bus_name = kwargs.get("EventBusName") or "default" + self.managed_by = kwargs.get("ManagedBy") # can only be set by AWS services + self.event_bus_name = kwargs.get("EventBusName") + self.created_by = ACCOUNT_ID self.targets = [] + @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=ACCOUNT_ID, + event_bus_name=event_bus_name, + name=self.name, + ) + @property def physical_resource_id(self): return self.name @@ -196,6 +207,8 @@ class Rule(CloudFormationModel): cls, resource_name, cloudformation_json, region_name ): properties = cloudformation_json["Properties"] + properties.setdefault("EventBusName", "default") + event_backend = events_backends[region_name] event_name = resource_name return event_backend.put_rule(name=event_name, **properties) @@ -682,6 +695,11 @@ class EventsBackend(BaseBackend): rule.event_bus_name = kwargs.get("EventBusName") or rule.event_bus_name def put_rule(self, name, **kwargs): + if kwargs.get("ScheduleExpression") and kwargs.get("EventBusName") != "default": + raise ValidationException( + "ScheduleExpression is supported only on the default event bus." + ) + if name in self.rules: self.update_rule(self.rules[name], **kwargs) new_rule = self.rules[name] @@ -932,6 +950,7 @@ class EventsBackend(BaseBackend): **{ "EventPattern": json.dumps(rule_event_pattern), "EventBusName": event_bus.name, + "ManagedBy": "prod.vhs.events.aws.internal", } ) self.put_targets( diff --git a/moto/events/responses.py b/moto/events/responses.py index cdf5ca052..bc016f858 100644 --- a/moto/events/responses.py +++ b/moto/events/responses.py @@ -25,7 +25,9 @@ class EventsHandler(BaseResponse): "Description": rule.description, "ScheduleExpression": rule.schedule_exp, "RoleArn": rule.role_arn, + "ManagedBy": rule.managed_by, "EventBusName": rule.event_bus_name, + "CreatedBy": rule.created_by, } @property @@ -168,10 +170,7 @@ class EventsHandler(BaseResponse): state = self._get_param("State") desc = self._get_param("Description") role_arn = self._get_param("RoleArn") - event_bus_name = self._get_param("EventBusName") - - if not name: - return self.error("ValidationException", "Parameter Name is required.") + event_bus_name = self._get_param("EventBusName", "default") if event_pattern: try: diff --git a/tests/test_events/test_events.py b/tests/test_events/test_events.py index a675a1d3f..3a08fd595 100644 --- a/tests/test_events/test_events.py +++ b/tests/test_events/test_events.py @@ -88,7 +88,6 @@ def test_put_rule(): "Name": "my-event", "ScheduleExpression": "rate(5 minutes)", "EventPattern": '{"source": ["test-source"]}', - "EventBusName": "test-bus", } client.put_rule(**rule_data) @@ -99,10 +98,34 @@ def test_put_rule(): rules[0]["Name"].should.equal(rule_data["Name"]) rules[0]["ScheduleExpression"].should.equal(rule_data["ScheduleExpression"]) rules[0]["EventPattern"].should.equal(rule_data["EventPattern"]) - rules[0]["EventBusName"].should.equal(rule_data["EventBusName"]) rules[0]["State"].should.equal("ENABLED") +@mock_events +def test_put_rule_error_schedule_expression_custom_event_bus(): + # given + client = boto3.client("events", "eu-central-1") + event_bus_name = "test-bus" + client.create_event_bus(Name=event_bus_name) + + # when + with pytest.raises(ClientError) as e: + client.put_rule( + Name="test-rule", + ScheduleExpression="rate(5 minutes)", + EventBusName=event_bus_name, + ) + + # then + ex = e.value + ex.operation_name.should.equal("PutRule") + ex.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400) + ex.response["Error"]["Code"].should.contain("ValidationException") + ex.response["Error"]["Message"].should.equal( + "ScheduleExpression is supported only on the default event bus." + ) + + @mock_events def test_list_rules(): client = generate_environment() @@ -118,15 +141,51 @@ def test_describe_rule(): client = generate_environment() response = client.describe_rule(Name=rule_name) - assert response is not None - assert response.get("Name") == rule_name - - rule_arn = response.get("Arn") - assert rule_arn == "arn:aws:events:us-west-2:{account}:rule/{name}".format( - account=ACCOUNT_ID, name=rule_name + response["Name"].should.equal(rule_name) + response["Arn"].should.equal( + "arn:aws:events:us-west-2:{0}:rule/{1}".format(ACCOUNT_ID, rule_name) ) +@mock_events +def test_describe_rule_with_event_bus_name(): + # given + client = boto3.client("events", "eu-central-1") + event_bus_name = "test-bus" + rule_name = "test-rule" + client.create_event_bus(Name=event_bus_name) + client.put_rule( + Name=rule_name, + EventPattern=json.dumps({"account": [ACCOUNT_ID]}), + State="DISABLED", + Description="test rule", + RoleArn="arn:aws:iam::{}:role/test-role".format(ACCOUNT_ID), + EventBusName=event_bus_name, + ) + + # when + response = client.describe_rule(Name=rule_name, EventBusName=event_bus_name) + + # then + response["Arn"].should.equal( + "arn:aws:events:eu-central-1:{0}:rule/{1}/{2}".format( + ACCOUNT_ID, event_bus_name, rule_name + ) + ) + response["CreatedBy"].should.equal(ACCOUNT_ID) + response["Description"].should.equal("test rule") + response["EventBusName"].should.equal(event_bus_name) + json.loads(response["EventPattern"]).should.equal({"account": [ACCOUNT_ID]}) + response["Name"].should.equal(rule_name) + response["RoleArn"].should.equal( + "arn:aws:iam::{}:role/test-role".format(ACCOUNT_ID) + ) + response["State"].should.equal("DISABLED") + + response.should_not.have.key("ManagedBy") + response.should_not.have.key("ScheduleExpression") + + @mock_events def test_enable_disable_rule(): rule_name = get_random_rule()["Name"] @@ -791,10 +850,11 @@ def test_list_tags_for_resource_error_unknown_arn(): def test_create_archive(): # given client = boto3.client("events", "eu-central-1") + archive_name = "test-archive" # when response = client.create_archive( - ArchiveName="test-archive", + ArchiveName=archive_name, EventSourceArn="arn:aws:events:eu-central-1:{}:event-bus/default".format( ACCOUNT_ID ), @@ -802,11 +862,31 @@ def test_create_archive(): # then response["ArchiveArn"].should.equal( - "arn:aws:events:eu-central-1:{}:archive/test-archive".format(ACCOUNT_ID) + "arn:aws:events:eu-central-1:{0}:archive/{1}".format(ACCOUNT_ID, archive_name) ) response["CreationTime"].should.be.a(datetime) response["State"].should.equal("ENABLED") + # check for archive rule existence + rule_name = "Events-Archive-{}".format(archive_name) + response = client.describe_rule(Name=rule_name) + + response["Arn"].should.equal( + "arn:aws:events:eu-central-1:{0}:rule/{1}".format(ACCOUNT_ID, rule_name) + ) + response["CreatedBy"].should.equal(ACCOUNT_ID) + response["EventBusName"].should.equal("default") + json.loads(response["EventPattern"]).should.equal( + {"replay-name": [{"exists": False}]} + ) + response["ManagedBy"].should.equal("prod.vhs.events.aws.internal") + response["Name"].should.equal(rule_name) + response["State"].should.equal("ENABLED") + + response.should_not.have.key("Description") + response.should_not.have.key("RoleArn") + response.should_not.have.key("ScheduleExpression") + @mock_events def test_create_archive_custom_event_bus():