Repository: getmoto/moto

Issue:
Support event signaling for known resources
When creating rules and targets for cloudwatch events, it would be great if those targets were triggered, similar to how mock_sns has the plumbing to trigger targets.

For example:

```python
    role = iam_client.create_role(
        RoleName='foobar',
        AssumeRolePolicyDocument='{}',
    )

    with open('src.zip', 'rb') as zip_file:
        func = lambda_client.create_function(
            FunctionName='foobar',
            Runtime='python3.8',
            Role=role['Arn'],
            Handler='handler.handler',
            Timeout=15,
            MemorySize=128,
            Publish=True,
            Code={'ZipFile': zip_file.read()},
        )

    rule = events_client.put_rule(
        Name='foobar',
        EventPattern='''{
            "source": [
                "aws.s3"
            ],
            "detail-type": [
                "AWS API Call via CloudTrail"
            ],
            "detail": {
                "eventSource": [
                    "s3.amazonaws.com"
                ],
                "eventName": [
                    "CreateBucket"
                ]
            }
        }''',
        State='ENABLED',
        RoleArn=role['Arn'],
    )

    target = events_client.put_targets(
        Rule='foobar',
        Targets=[
            {
                'Id': 'foobar',
                'Arn': func['FunctionArn'],
                'RoleArn': role['Arn'],
            }
        ],
    )
    
    bucket = s3_client.create_bucket(
        ACL='public-read-write',
        Bucket='foobar',
    )

    start = time.time()
    while (time.time() - start) < 30:
        result = logs_client.describe_log_streams(
			logGroupName='/aws/lambda/foobar',
		)

        log_streams = result.get('logStreams')
        if not log_streams:
            time.sleep(1)
            continue

        print(log_streams)

    assert False, "Test Failed"
```

This fails because the lambda is never trigger, even though testing with `invoke()` works with docker as expected.

If I am doing something wrong, please let me know.


Discussion:

Resolving patch:
diff --git a/moto/events/models.py b/moto/events/models.py
--- a/moto/events/models.py
+++ b/moto/events/models.py
@@ -824,6 +824,9 @@ 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
@@ -921,6 +924,14 @@ def parse(self):
 
 
 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\(.*\)")
diff --git a/moto/events/notifications.py b/moto/events/notifications.py
new file mode 100644
--- /dev/null
+++ b/moto/events/notifications.py
@@ -0,0 +1,65 @@
+import json
+
+
+_EVENT_S3_OBJECT_CREATED = {
+    "version": "0",
+    "id": "17793124-05d4-b198-2fde-7ededc63b103",
+    "detail-type": "Object Created",
+    "source": "aws.s3",
+    "account": "123456789012",
+    "time": "2021-11-12T00:00:00Z",
+    "region": None,
+    "resources": [],
+    "detail": None,
+}
+
+
+def send_notification(source, event_name, region, resources, detail):
+    try:
+        _send_safe_notification(source, event_name, region, resources, detail)
+    except:  # noqa
+        # If anything goes wrong, we should never fail
+        pass
+
+
+def _send_safe_notification(source, event_name, region, resources, detail):
+    from .models import events_backends
+
+    event = None
+    if source == "aws.s3" and event_name == "CreateBucket":
+        event = _EVENT_S3_OBJECT_CREATED.copy()
+        event["region"] = region
+        event["resources"] = resources
+        event["detail"] = detail
+
+    if event is None:
+        return
+
+    for backend in events_backends.values():
+        applicable_targets = []
+        for rule in backend.rules.values():
+            if rule.state != "ENABLED":
+                continue
+            pattern = rule.event_pattern.get_pattern()
+            if source in pattern.get("source", []):
+                if event_name in pattern.get("detail", {}).get("eventName", []):
+                    applicable_targets.extend(rule.targets)
+
+        for target in applicable_targets:
+            if target.get("Arn", "").startswith("arn:aws:lambda"):
+                _invoke_lambda(target.get("Arn"), event=event)
+
+
+def _invoke_lambda(fn_arn, event):
+    from moto.awslambda import lambda_backends
+
+    lmbda_region = fn_arn.split(":")[3]
+
+    body = json.dumps(event)
+    lambda_backends[lmbda_region].invoke(
+        function_name=fn_arn,
+        qualifier=None,
+        body=body,
+        headers=dict(),
+        response_headers=dict(),
+    )
diff --git a/moto/s3/models.py b/moto/s3/models.py
--- a/moto/s3/models.py
+++ b/moto/s3/models.py
@@ -58,6 +58,7 @@
 from .cloud_formation import cfn_to_api_encryption, is_replacement_update
 from . import notifications
 from .utils import clean_key_name, _VersionedKeyStore, undo_clean_key_name
+from ..events.notifications import send_notification as events_send_notification
 from ..settings import get_s3_default_key_buffer_size, S3_UPLOAD_PART_MIN_SIZE
 
 MAX_BUCKET_NAME_LENGTH = 63
@@ -1463,6 +1464,23 @@ def create_bucket(self, bucket_name, region_name):
         new_bucket = FakeBucket(name=bucket_name, region_name=region_name)
 
         self.buckets[bucket_name] = new_bucket
+
+        notification_detail = {
+            "version": "0",
+            "bucket": {"name": bucket_name},
+            "request-id": "N4N7GDK58NMKJ12R",
+            "requester": get_account_id(),
+            "source-ip-address": "1.2.3.4",
+            "reason": "PutObject",
+        }
+        events_send_notification(
+            source="aws.s3",
+            event_name="CreateBucket",
+            region=region_name,
+            resources=[f"arn:aws:s3:::{bucket_name}"],
+            detail=notification_detail,
+        )
+
         return new_bucket
 
     def list_buckets(self):


Test patch:
diff --git a/tests/test_events/test_events_lambdatriggers_integration.py b/tests/test_events/test_events_lambdatriggers_integration.py
new file mode 100644
--- /dev/null
+++ b/tests/test_events/test_events_lambdatriggers_integration.py
@@ -0,0 +1,335 @@
+import boto3
+import json
+
+from moto import mock_events, mock_iam, mock_lambda, mock_logs, mock_s3
+from moto.core import ACCOUNT_ID
+from ..test_awslambda.utilities import get_test_zip_file1, wait_for_log_msg
+
+
+@mock_events
+@mock_iam
+@mock_lambda
+@mock_logs
+@mock_s3
+def test_creating_bucket__invokes_lambda():
+    iam_client = boto3.client("iam", "us-east-1")
+    lambda_client = boto3.client("lambda", "us-east-1")
+    events_client = boto3.client("events", "us-east-1")
+    s3_client = boto3.client("s3", "us-east-1")
+
+    role = iam_client.create_role(
+        RoleName="foobar",
+        AssumeRolePolicyDocument="{}",
+    )["Role"]
+
+    func = lambda_client.create_function(
+        FunctionName="foobar",
+        Runtime="python3.8",
+        Role=role["Arn"],
+        Handler="lambda_function.lambda_handler",
+        Code={"ZipFile": get_test_zip_file1()},
+    )
+
+    events_client.put_rule(
+        Name="foobarrule",
+        EventPattern="""{
+                "source": [
+                    "aws.s3"
+                ],
+                "detail-type": [
+                    "AWS API Call via CloudTrail"
+                ],
+                "detail": {
+                    "eventSource": [
+                        "s3.amazonaws.com"
+                    ],
+                    "eventName": [
+                        "CreateBucket"
+                    ]
+                }
+            }""",
+        State="ENABLED",
+        RoleArn=role["Arn"],
+    )
+
+    events_client.put_targets(
+        Rule="foobarrule",
+        Targets=[
+            {
+                "Id": "n/a",
+                "Arn": func["FunctionArn"],
+                "RoleArn": role["Arn"],
+            }
+        ],
+    )
+
+    bucket_name = "foobar"
+    s3_client.create_bucket(
+        ACL="public-read-write",
+        Bucket=bucket_name,
+    )
+
+    expected_msg = '"detail-type":"Object Created"'
+    log_group = f"/aws/lambda/{bucket_name}"
+    msg_showed_up, all_logs = wait_for_log_msg(expected_msg, log_group, wait_time=5)
+
+    assert (
+        msg_showed_up
+    ), "Lambda was not invoked after creating an S3 bucket. All logs: " + str(all_logs)
+
+    event = json.loads(list([line for line in all_logs if expected_msg in line])[-1])
+
+    event.should.have.key("detail-type").equals("Object Created")
+    event.should.have.key("source").equals("aws.s3")
+    event.should.have.key("account").equals(ACCOUNT_ID)
+    event.should.have.key("time")
+    event.should.have.key("region").equals("us-east-1")
+    event.should.have.key("resources").equals([f"arn:aws:s3:::{bucket_name}"])
+
+
+@mock_events
+@mock_iam
+@mock_lambda
+@mock_logs
+@mock_s3
+def test_create_disabled_rule():
+    iam_client = boto3.client("iam", "us-east-1")
+    lambda_client = boto3.client("lambda", "us-east-1")
+    events_client = boto3.client("events", "us-east-1")
+    s3_client = boto3.client("s3", "us-east-1")
+
+    role = iam_client.create_role(
+        RoleName="foobar",
+        AssumeRolePolicyDocument="{}",
+    )["Role"]
+
+    func = lambda_client.create_function(
+        FunctionName="foobar",
+        Runtime="python3.8",
+        Role=role["Arn"],
+        Handler="lambda_function.lambda_handler",
+        Code={"ZipFile": get_test_zip_file1()},
+    )
+
+    events_client.put_rule(
+        Name="foobarrule",
+        EventPattern="""{
+                    "source": [
+                        "aws.s3"
+                    ],
+                    "detail-type": [
+                        "AWS API Call via CloudTrail"
+                    ],
+                    "detail": {
+                        "eventSource": [
+                            "s3.amazonaws.com"
+                        ],
+                        "eventName": [
+                            "CreateBucket"
+                        ]
+                    }
+                }""",
+        State="DISABLED",
+        RoleArn=role["Arn"],
+    )
+
+    events_client.put_targets(
+        Rule="foobarrule",
+        Targets=[
+            {
+                "Id": "n/a",
+                "Arn": func["FunctionArn"],
+                "RoleArn": role["Arn"],
+            }
+        ],
+    )
+
+    bucket_name = "foobar"
+    s3_client.create_bucket(
+        ACL="public-read-write",
+        Bucket=bucket_name,
+    )
+
+    expected_msg = '"detail-type":"Object Created"'
+    log_group = f"/aws/lambda/{bucket_name}"
+    msg_showed_up, _ = wait_for_log_msg(expected_msg, log_group, wait_time=5)
+    msg_showed_up.should.equal(False)
+
+
+@mock_events
+@mock_iam
+@mock_logs
+@mock_s3
+def test_create_rule_for_unsupported_target_arn():
+    iam_client = boto3.client("iam", "us-east-1")
+    events_client = boto3.client("events", "us-east-1")
+    s3_client = boto3.client("s3", "us-east-1")
+
+    role = iam_client.create_role(
+        RoleName="foobar",
+        AssumeRolePolicyDocument="{}",
+    )["Role"]
+
+    events_client.put_rule(
+        Name="foobarrule",
+        EventPattern="""{
+                    "source": [
+                        "aws.s3"
+                    ],
+                    "detail-type": [
+                        "AWS API Call via CloudTrail"
+                    ],
+                    "detail": {
+                        "eventSource": [
+                            "s3.amazonaws.com"
+                        ],
+                        "eventName": [
+                            "CreateBucket"
+                        ]
+                    }
+                }""",
+        State="ENABLED",
+        RoleArn=role["Arn"],
+    )
+
+    events_client.put_targets(
+        Rule="foobarrule",
+        Targets=[
+            {
+                "Id": "n/a",
+                "Arn": "arn:aws:unknown",
+                "RoleArn": role["Arn"],
+            }
+        ],
+    )
+
+    bucket_name = "foobar"
+    s3_client.create_bucket(
+        ACL="public-read-write",
+        Bucket=bucket_name,
+    )
+
+    expected_msg = '"detail-type":"Object Created"'
+    log_group = f"/aws/lambda/{bucket_name}"
+    msg_showed_up, _ = wait_for_log_msg(expected_msg, log_group, wait_time=5)
+    msg_showed_up.should.equal(False)
+
+
+@mock_events
+@mock_iam
+@mock_lambda
+@mock_logs
+@mock_s3
+def test_creating_bucket__but_invoke_lambda_on_create_object():
+    iam_client = boto3.client("iam", "us-east-1")
+    lambda_client = boto3.client("lambda", "us-east-1")
+    events_client = boto3.client("events", "us-east-1")
+    s3_client = boto3.client("s3", "us-east-1")
+
+    role = iam_client.create_role(
+        RoleName="foobar",
+        AssumeRolePolicyDocument="{}",
+    )["Role"]
+
+    func = lambda_client.create_function(
+        FunctionName="foobar",
+        Runtime="python3.8",
+        Role=role["Arn"],
+        Handler="lambda_function.lambda_handler",
+        Code={"ZipFile": get_test_zip_file1()},
+    )
+
+    events_client.put_rule(
+        Name="foobarrule",
+        EventPattern="""{
+                "source": [
+                    "aws.s3"
+                ],
+                "detail": {
+                    "eventSource": [
+                        "s3.amazonaws.com"
+                    ],
+                    "eventName": [
+                        "CreateObject"
+                    ]
+                }
+            }""",
+        State="ENABLED",
+        RoleArn=role["Arn"],
+    )
+
+    events_client.put_targets(
+        Rule="foobarrule",
+        Targets=[
+            {
+                "Id": "n/a",
+                "Arn": func["FunctionArn"],
+                "RoleArn": role["Arn"],
+            }
+        ],
+    )
+
+    bucket_name = "foobar"
+    s3_client.create_bucket(
+        ACL="public-read-write",
+        Bucket=bucket_name,
+    )
+
+    expected_msg = '"detail-type":"Object Created"'
+    log_group = f"/aws/lambda/{bucket_name}"
+    msg_showed_up, _ = wait_for_log_msg(expected_msg, log_group, wait_time=5)
+    msg_showed_up.should.equal(False)
+
+
+@mock_events
+@mock_iam
+@mock_s3
+def test_creating_bucket__succeeds_despite_unknown_lambda():
+    iam_client = boto3.client("iam", "us-east-1")
+    events_client = boto3.client("events", "us-east-1")
+    s3_client = boto3.client("s3", "us-east-1")
+
+    role = iam_client.create_role(
+        RoleName="foobar",
+        AssumeRolePolicyDocument="{}",
+    )["Role"]
+
+    events_client.put_rule(
+        Name="foobarrule",
+        EventPattern="""{
+                "source": [
+                    "aws.s3"
+                ],
+                "detail-type": [
+                    "AWS API Call via CloudTrail"
+                ],
+                "detail": {
+                    "eventSource": [
+                        "s3.amazonaws.com"
+                    ],
+                    "eventName": [
+                        "CreateBucket"
+                    ]
+                }
+            }""",
+        State="ENABLED",
+        RoleArn=role["Arn"],
+    )
+
+    events_client.put_targets(
+        Rule="foobarrule",
+        Targets=[
+            {
+                "Id": "n/a",
+                "Arn": "arn:aws:lambda:unknown",
+                "RoleArn": role["Arn"],
+            }
+        ],
+    )
+
+    bucket_name = "foobar"
+    bucket = s3_client.create_bucket(
+        ACL="public-read-write",
+        Bucket=bucket_name,
+    )
+    bucket.shouldnt.equal(None)