Add lambda_bucket_event module (#58059)

This commit is contained in:
Aljaž Košir 2019-07-18 19:54:01 +02:00 committed by Jill R
parent 1c1da3c11d
commit 42073b6331
8 changed files with 879 additions and 1 deletions

View file

@ -13,6 +13,7 @@
"s3:GetBucketVersioning",
"s3:GetEncryptionConfiguration",
"s3:GetObject",
"s3:GetBucketNotification",
"s3:HeadBucket",
"s3:ListBucket",
"s3:PutBucketAcl",
@ -22,7 +23,8 @@
"s3:PutBucketVersioning",
"s3:PutEncryptionConfiguration",
"s3:PutObject",
"s3:PutObjectAcl"
"s3:PutObjectAcl",
"s3:PutBucketNotification"
],
"Effect": "Allow",
"Resource": [

View file

@ -0,0 +1,267 @@
#!/usr/bin/python
# (c) 2019, XLAB d.o.o <www.xlab.si>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
ANSIBLE_METADATA = {'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'}
DOCUMENTATION = '''
---
module: s3_bucket_notification
short_description: Creates, updates or deletes S3 Bucket notification for lambda
description:
- This module allows the management of AWS Lambda function bucket event mappings via the
Ansible framework. Use module M(lambda) to manage the lambda function itself, M(lambda_alias)
to manage function aliases and M(lambda_policy) to modify lambda permissions.
notes:
- This module heavily depends on M(lambda_policy) as you need to allow C(lambda:InvokeFunction)
permission for your lambda function.
version_added: "2.9"
author:
- XLAB d.o.o. (@xlab-si)
- Aljaz Kosir (@aljazkosir)
- Miha Plesko (@miha-plesko)
options:
event_name:
description:
- unique name for event notification on bucket
required: True
type: str
lambda_function_arn:
description:
- The ARN of the lambda function.
aliases: ['function_arn']
type: str
bucket_name:
description:
- S3 bucket name
required: True
type: str
state:
description:
- Describes the desired state.
required: true
default: "present"
choices: ["present", "absent"]
type: str
lambda_alias:
description:
- Name of the Lambda function alias. Mutually exclusive with C(lambda_version).
required: false
type: str
lambda_version:
description:
- Version of the Lambda function. Mutually exclusive with C(lambda_alias).
required: false
type: int
events:
description:
- Events that you want to be triggering notifications. You can select multiple events to send
to the same destination, you can set up different events to send to different destinations,
and you can set up a prefix or suffix for an event. However, for each bucket,
individual events cannot have multiple configurations with overlapping prefixes or
suffixes that could match the same object key.
required: True
choices: ['s3:ObjectCreated:*', 's3:ObjectCreated:Put', 's3:ObjectCreated:Post',
's3:ObjectCreated:Copy', 's3:ObjectCreated:CompleteMultipartUpload',
's3:ObjectRemoved:*', 's3:ObjectRemoved:Delete',
's3:ObjectRemoved:DeleteMarkerCreated', 's3:ObjectRestore:Post',
's3:ObjectRestore:Completed', 's3:ReducedRedundancyLostObject']
type: list
prefix:
description:
- Optional prefix to limit the notifications to objects with keys that start with matching
characters.
required: false
type: str
suffix:
description:
- Optional suffix to limit the notifications to objects with keys that end with matching
characters.
required: false
type: str
requirements:
- boto3
extends_documentation_fragment:
- aws
- ec2
'''
EXAMPLES = '''
---
# Example that creates a lambda event notification for a bucket
- hosts: localhost
gather_facts: no
tasks:
- name: Process jpg image
s3_bucket_notification:
state: present
event_name: on_file_add_or_remove
bucket_name: test-bucket
function_name: arn:aws:lambda:us-east-2:526810320200:function:test-lambda
events: ["s3:ObjectCreated:*", "s3:ObjectRemoved:*"]
prefix: images/
suffix: .jpg
'''
RETURN = '''
notification_configuration:
description: list of currently applied notifications
returned: success
type: list
'''
from ansible.module_utils.aws.core import AnsibleAWSModule
from ansible.module_utils.ec2 import camel_dict_to_snake_dict
try:
from botocore.exceptions import ClientError, BotoCoreError
except ImportError:
pass # will be protected by AnsibleAWSModule
class AmazonBucket:
def __init__(self, client, bucket_name):
self.client = client
self.bucket_name = bucket_name
self._full_config_cache = None
def full_config(self):
if self._full_config_cache is None:
self._full_config_cache = [Config.from_api(cfg) for cfg in
self.client.get_bucket_notification_configuration(
Bucket=self.bucket_name).get(
'LambdaFunctionConfigurations', list())]
return self._full_config_cache
def current_config(self, config_name):
for config in self.full_config():
if config.raw['Id'] == config_name:
return config
def apply_config(self, desired):
configs = [cfg.raw for cfg in self.full_config() if cfg.name != desired.raw['Id']]
configs.append(desired.raw)
self._upload_bucket_config(configs)
return configs
def delete_config(self, desired):
configs = [cfg.raw for cfg in self.full_config() if cfg.name != desired.raw['Id']]
self._upload_bucket_config(configs)
return configs
def _upload_bucket_config(self, config):
self.client.put_bucket_notification_configuration(
Bucket=self.bucket_name,
NotificationConfiguration={
'LambdaFunctionConfigurations': config
})
class Config:
def __init__(self, content):
self._content = content
self.name = content['Id']
@property
def raw(self):
return self._content
def __eq__(self, other):
if other:
return self.raw == other.raw
return False
@classmethod
def from_params(cls, **params):
function_arn = params['lambda_function_arn']
qualifier = None
if params['lambda_version'] > 0:
qualifier = str(params['lambda_version'])
elif params['lambda_alias']:
qualifier = str(params['lambda_alias'])
if qualifier:
params['lambda_function_arn'] = '{0}:{1}'.format(function_arn, qualifier)
return cls({
'Id': params['event_name'],
'LambdaFunctionArn': params['lambda_function_arn'],
'Events': sorted(params['events']),
'Filter': {
'Key': {
'FilterRules': [{
'Name': 'Prefix',
'Value': params['prefix']
}, {
'Name': 'Suffix',
'Value': params['suffix']
}]
}
}
})
@classmethod
def from_api(cls, config):
return cls(config)
def main():
event_types = ['s3:ObjectCreated:*', 's3:ObjectCreated:Put', 's3:ObjectCreated:Post',
's3:ObjectCreated:Copy', 's3:ObjectCreated:CompleteMultipartUpload',
's3:ObjectRemoved:*', 's3:ObjectRemoved:Delete',
's3:ObjectRemoved:DeleteMarkerCreated', 's3:ObjectRestore:Post',
's3:ObjectRestore:Completed', 's3:ReducedRedundancyLostObject']
argument_spec = dict(
state=dict(default='present', choices=['present', 'absent']),
event_name=dict(required=True),
lambda_function_arn=dict(aliases=['function_arn']),
bucket_name=dict(required=True),
events=dict(type='list', default=[], choices=event_types),
prefix=dict(default=''),
suffix=dict(default=''),
lambda_alias=dict(),
lambda_version=dict(type='int', default=0),
)
module = AnsibleAWSModule(
argument_spec=argument_spec,
supports_check_mode=True,
mutually_exclusive=[['lambda_alias', 'lambda_version']],
required_if=[['state', 'present', ['events']]]
)
bucket = AmazonBucket(module.client('s3'), module.params['bucket_name'])
current = bucket.current_config(module.params['event_name'])
desired = Config.from_params(**module.params)
notification_configuration = [cfg.raw for cfg in bucket.full_config()]
state = module.params['state']
try:
if (state == 'present' and current == desired) or (state == 'absent' and not current):
changed = False
elif module.check_mode:
changed = True
elif state == 'present':
changed = True
notification_configuration = bucket.apply_config(desired)
elif state == 'absent':
changed = True
notification_configuration = bucket.delete_config(desired)
except (ClientError, BotoCoreError) as e:
module.fail_json(msg='{0}'.format(e))
module.exit_json(**dict(changed=changed,
notification_configuration=[camel_dict_to_snake_dict(cfg) for cfg in
notification_configuration]))
if __name__ == '__main__':
main()

View file

@ -0,0 +1,2 @@
cloud/aws
shippable/aws/group2

View file

@ -0,0 +1,3 @@
---
# defaults file for aws_lambda test
lambda_function_name: '{{resource_prefix}}'

View file

@ -0,0 +1,8 @@
import json
def lambda_handler(event, context):
return {
'statusCode': 200,
'body': json.dumps('Hello from Lambda!')
}

View file

@ -0,0 +1,3 @@
dependencies:
- prepare_tests
- setup_ec2

View file

@ -0,0 +1,335 @@
---
# ============================================================
- name: set up aws connection info
set_fact:
aws_connection_info: &aws_connection_info
aws_access_key: "{{ aws_access_key }}"
aws_secret_key: "{{ aws_secret_key }}"
security_token: "{{ security_token }}"
region: "{{ aws_region }}"
no_log: yes
# ============================================================
- name: test add s3 bucket notification
block:
- name: move lambda into place for archive module
copy:
src: "mini_lambda.py"
dest: "{{output_dir}}/mini_lambda.py"
- name: bundle lambda into a zip
archive:
format: zip
path: "{{output_dir}}/mini_lambda.py"
dest: "{{output_dir}}/mini_lambda.zip"
register: function_res
- name: register bucket
s3_bucket:
name: "{{resource_prefix}}-bucket"
state: present
<<: *aws_connection_info
register: bucket_info
- name: register lambda
lambda:
name: "{{resource_prefix}}-lambda"
state: present
role: "ansible_lambda_role"
runtime: "python3.7"
zip_file: "{{function_res.dest}}"
handler: "lambda_function.lambda_handler"
memory_size: "128"
timeout: "30"
<<: *aws_connection_info
register: lambda_info
- name: register notification without invoke permissions
s3_bucket_notification:
state: present
event_name: "{{resource_prefix}}-on_file_add_or_remove"
bucket_name: "{{resource_prefix}}-bucket"
lambda_function_arn: "{{ lambda_info.configuration.function_arn }}"
events: ["s3:ObjectCreated:*", "s3:ObjectRemoved:*"]
prefix: images/
suffix: .jpg
<<: *aws_connection_info
register: result
ignore_errors: true
- name: assert nice message returned
assert:
that:
- result is failed
- result.msg != 'MODULE FAILURE'
- name: Add invocation permission of Lambda function on AWS
lambda_policy:
function_name: "{{ lambda_info.configuration.function_arn }}"
statement_id: allow_lambda_invoke
action: lambda:InvokeFunction
principal: "s3.amazonaws.com"
source_arn: "arn:aws:s3:::{{bucket_info.name}}"
<<: *aws_connection_info
- name: register s3 bucket notification
s3_bucket_notification:
state: present
event_name: "{{resource_prefix}}-on_file_add_or_remove"
bucket_name: "{{resource_prefix}}-bucket"
lambda_function_arn: "{{ lambda_info.configuration.function_arn }}"
events: ["s3:ObjectCreated:*", "s3:ObjectRemoved:*"]
prefix: images/
suffix: .jpg
<<: *aws_connection_info
register: result
- name: assert result.changed == True
assert:
that:
- result.changed == True
# ============================================================
- name: test check_mode without change
s3_bucket_notification:
state: present
event_name: "{{resource_prefix}}-on_file_add_or_remove"
bucket_name: "{{resource_prefix}}-bucket"
lambda_function_arn: "{{ lambda_info.configuration.function_arn }}"
events: ["s3:ObjectCreated:*", "s3:ObjectRemoved:*"]
prefix: images/
suffix: .jpg
<<: *aws_connection_info
register: result
check_mode: yes
- name: assert result.changed == False
assert:
that:
- result.changed == False
- name: test check_mode change events
s3_bucket_notification:
state: present
event_name: "{{resource_prefix}}-on_file_add_or_remove"
bucket_name: "{{resource_prefix}}-bucket"
lambda_function_arn: "{{ lambda_info.configuration.function_arn }}"
events: ["s3:ObjectCreated:*"]
prefix: images/
suffix: .jpg
<<: *aws_connection_info
register: result
check_mode: yes
- name: assert result.changed == True
assert:
that:
- result.changed == True
- name: test that check_mode didn't change events
s3_bucket_notification:
state: present
event_name: "{{resource_prefix}}-on_file_add_or_remove"
bucket_name: "{{resource_prefix}}-bucket"
lambda_function_arn: "{{ lambda_info.configuration.function_arn }}"
events: ["s3:ObjectCreated:*", "s3:ObjectRemoved:*"]
prefix: images/
suffix: .jpg
<<: *aws_connection_info
register: result
- name: assert result.changed == False
assert:
that:
- result.changed == False
# ============================================================
- name: test mutually exclusive parameters
s3_bucket_notification:
state: present
event_name: "{{resource_prefix}}-on_file_add_or_remove"
bucket_name: "{{resource_prefix}}-bucket"
lambda_function_arn: "{{ lambda_info.configuration.function_arn }}"
events: ["s3:ObjectCreated:Post"]
prefix: photos/
suffix: .gif
lambda_version: 0
lambda_alias: 0
<<: *aws_connection_info
register: result
ignore_errors: true
- name: assert task failed
assert:
that:
- result is failed
- "result.msg == 'parameters are mutually exclusive: lambda_alias|lambda_version'"
# ============================================================
# Test configuration changes
- name: test configuration change on suffix
s3_bucket_notification:
state: present
event_name: "{{resource_prefix}}-on_file_add_or_remove"
bucket_name: "{{resource_prefix}}-bucket"
lambda_function_arn: "{{ lambda_info.configuration.function_arn }}"
events: ["s3:ObjectCreated:*", "s3:ObjectRemoved:*"]
prefix: images/
suffix: .gif
<<: *aws_connection_info
register: result
- name: assert result.changed == True
assert:
that:
- result.changed == True
- name: test configuration change on prefix
s3_bucket_notification:
state: present
event_name: "{{resource_prefix}}-on_file_add_or_remove"
bucket_name: "{{resource_prefix}}-bucket"
lambda_function_arn: "{{ lambda_info.configuration.function_arn }}"
events: ["s3:ObjectCreated:*", "s3:ObjectRemoved:*"]
prefix: photos/
suffix: .gif
<<: *aws_connection_info
register: result
- name: assert result.changed == True
assert:
that:
- result.changed == True
- name: test configuration change on new events added
s3_bucket_notification:
state: present
event_name: "{{resource_prefix}}-on_file_add_or_remove"
bucket_name: "{{resource_prefix}}-bucket"
lambda_function_arn: "{{ lambda_info.configuration.function_arn }}"
events: ["s3:ObjectCreated:*", "s3:ObjectRemoved:*", "s3:ObjectRestore:Post"]
prefix: photos/
suffix: .gif
<<: *aws_connection_info
register: result
- name: assert result.changed == True
assert:
that:
- result.changed == True
- name: test configuration change on events removed
s3_bucket_notification:
state: present
event_name: "{{resource_prefix}}-on_file_add_or_remove"
bucket_name: "{{resource_prefix}}-bucket"
lambda_function_arn: "{{ lambda_info.configuration.function_arn }}"
events: ["s3:ObjectCreated:Post"]
prefix: photos/
suffix: .gif
<<: *aws_connection_info
register: result
- name: assert result.changed == True
assert:
that:
- result.changed == True
# ============================================================
# Test idempotency of CRUD
- name: change events
s3_bucket_notification:
state: present
event_name: "{{resource_prefix}}-on_file_add_or_remove"
bucket_name: "{{resource_prefix}}-bucket"
lambda_function_arn: "{{ lambda_info.configuration.function_arn }}"
events: ["s3:ObjectCreated:*", "s3:ObjectRemoved:*", "s3:ObjectRestore:Post"]
prefix: photos/
suffix: .gif
<<: *aws_connection_info
register: result
- name: test that event order does not matter
s3_bucket_notification:
state: present
event_name: "{{resource_prefix}}-on_file_add_or_remove"
bucket_name: "{{resource_prefix}}-bucket"
lambda_function_arn: "{{ lambda_info.configuration.function_arn }}"
events: ["s3:ObjectRestore:Post", "s3:ObjectRemoved:*", "s3:ObjectCreated:*"]
prefix: photos/
suffix: .gif
<<: *aws_connection_info
register: result
- name: assert result.changed == False
assert:
that:
- result.changed == False
- name: test that configuration is the same as previous task
s3_bucket_notification:
state: present
event_name: "{{resource_prefix}}-on_file_add_or_remove"
bucket_name: "{{resource_prefix}}-bucket"
lambda_function_arn: "{{ lambda_info.configuration.function_arn }}"
events: ["s3:ObjectCreated:*", "s3:ObjectRemoved:*", "s3:ObjectRestore:Post"]
prefix: photos/
suffix: .gif
<<: *aws_connection_info
register: result
- name: assert result.changed == False
assert:
that:
- result.changed == False
- name: test remove notification
s3_bucket_notification:
state: absent
event_name: "{{resource_prefix}}-on_file_add_or_remove"
bucket_name: "{{resource_prefix}}-bucket"
<<: *aws_connection_info
register: result
- name: assert result.changed == True
assert:
that:
- result.changed == True
- name: test that events is already removed
s3_bucket_notification:
state: absent
event_name: "{{resource_prefix}}-on_file_add_or_remove"
bucket_name: "{{resource_prefix}}-bucket"
<<: *aws_connection_info
register: result
- name: assert result.changed == False
assert:
that:
- result.changed == False
always:
- name: clean-up bucket
s3_bucket:
name: "{{resource_prefix}}-bucket"
state: absent
<<: *aws_connection_info
- name: clean-up lambda
lambda:
name: "{{resource_prefix}}-lambda"
state: absent
<<: *aws_connection_info
# ============================================================
-
- block:
# ============================================================
- name: test with no parameters except state absent
s3_bucket_notification:
state=absent
register: result
ignore_errors: true
- name: assert failure when called with no parameters
assert:
that:
- 'result.failed'
- 'result.msg.startswith("missing required arguments: event_name, bucket_name")'
# ============================================================
- name: test abesnt
s3_bucket_notification:
state=absent
register: result
ignore_errors: true
- name: assert failure when called with no parameters
assert:
that:
- 'result.failed'
- 'result.msg.startswith("missing required arguments: event_name, bucket_name")'

View file

@ -0,0 +1,258 @@
import pytest
from units.compat.mock import MagicMock, patch
from units.modules.utils import AnsibleExitJson, AnsibleFailJson, ModuleTestCase, set_module_args
from ansible.modules.cloud.amazon.s3_bucket_notification import AmazonBucket, Config
from ansible.modules.cloud.amazon import s3_bucket_notification
try:
from botocore.exceptions import ClientError, ParamValidationError, BotoCoreError
except ImportError:
pass
class TestAmazonBucketOperations:
def test_current_config(self):
api_config = {
'Id': 'test-id',
'LambdaFunctionArn': 'test-arn',
'Events': [],
'Filter': {
'Key': {
'FilterRules': [{
'Name': 'Prefix',
'Value': ''
}, {
'Name': 'Suffix',
'Value': ''
}]
}
}
}
client = MagicMock()
client.get_bucket_notification_configuration.return_value = {
'LambdaFunctionConfigurations': [api_config]
}
bucket = AmazonBucket(client, 'test-bucket')
current = bucket.current_config('test-id')
assert current.raw == api_config
assert client.get_bucket_notification_configuration.call_count == 1
def test_current_config_empty(self):
client = MagicMock()
client.get_bucket_notification_configuration.return_value = {
'LambdaFunctionConfigurations': []
}
bucket = AmazonBucket(client, 'test-bucket')
current = bucket.current_config('test-id')
assert current is None
assert client.get_bucket_notification_configuration.call_count == 1
def test_apply_invalid_config(self):
client = MagicMock()
client.get_bucket_notification_configuration.return_value = {
'LambdaFunctionConfigurations': []
}
client.put_bucket_notification_configuration.side_effect = ClientError({}, '')
bucket = AmazonBucket(client, 'test-bucket')
config = Config.from_params(**{
'event_name': 'test_event',
'lambda_function_arn': 'lambda_arn',
'lambda_version': 1,
'events': ['s3:ObjectRemoved:*', 's3:ObjectCreated:*'],
'prefix': '',
'suffix': ''
})
with pytest.raises(ClientError):
bucket.apply_config(config)
def test_apply_config(self):
client = MagicMock()
client.get_bucket_notification_configuration.return_value = {
'LambdaFunctionConfigurations': []
}
bucket = AmazonBucket(client, 'test-bucket')
config = Config.from_params(**{
'event_name': 'test_event',
'lambda_function_arn': 'lambda_arn',
'lambda_version': 1,
'events': ['s3:ObjectRemoved:*', 's3:ObjectCreated:*'],
'prefix': '',
'suffix': ''
})
bucket.apply_config(config)
assert client.get_bucket_notification_configuration.call_count == 1
assert client.put_bucket_notification_configuration.call_count == 1
def test_apply_config_add_event(self):
api_config = {
'Id': 'test-id',
'LambdaFunctionArn': 'test-arn',
'Events': ['s3:ObjectRemoved:*'],
'Filter': {
'Key': {
'FilterRules': [{
'Name': 'Prefix',
'Value': ''
}, {
'Name': 'Suffix',
'Value': ''
}]
}
}
}
client = MagicMock()
client.get_bucket_notification_configuration.return_value = {
'LambdaFunctionConfigurations': [api_config]
}
bucket = AmazonBucket(client, 'test-bucket')
config = Config.from_params(**{
'event_name': 'test-id',
'lambda_function_arn': 'test-arn',
'lambda_version': 1,
'events': ['s3:ObjectRemoved:*', 's3:ObjectCreated:*'],
'prefix': '',
'suffix': ''
})
bucket.apply_config(config)
assert client.get_bucket_notification_configuration.call_count == 1
assert client.put_bucket_notification_configuration.call_count == 1
client.put_bucket_notification_configuration.assert_called_with(
Bucket='test-bucket',
NotificationConfiguration={
'LambdaFunctionConfigurations': [{
'Id': 'test-id',
'LambdaFunctionArn': 'test-arn:1',
'Events': ['s3:ObjectCreated:*', 's3:ObjectRemoved:*'],
'Filter': {
'Key': {
'FilterRules': [{
'Name': 'Prefix',
'Value': ''
}, {
'Name': 'Suffix',
'Value': ''
}]
}
}
}]
}
)
def test_delete_config(self):
api_config = {
'Id': 'test-id',
'LambdaFunctionArn': 'test-arn',
'Events': [],
'Filter': {
'Key': {
'FilterRules': [{
'Name': 'Prefix',
'Value': ''
}, {
'Name': 'Suffix',
'Value': ''
}]
}
}
}
client = MagicMock()
client.get_bucket_notification_configuration.return_value = {
'LambdaFunctionConfigurations': [api_config]
}
bucket = AmazonBucket(client, 'test-bucket')
config = Config.from_params(**{
'event_name': 'test-id',
'lambda_function_arn': 'lambda_arn',
'lambda_version': 1,
'events': [],
'prefix': '',
'suffix': ''
})
bucket.delete_config(config)
assert client.get_bucket_notification_configuration.call_count == 1
assert client.put_bucket_notification_configuration.call_count == 1
client.put_bucket_notification_configuration.assert_called_with(
Bucket='test-bucket',
NotificationConfiguration={'LambdaFunctionConfigurations': []}
)
class TestConfig:
def test_config_from_params(self):
config = Config({
'Id': 'test-id',
'LambdaFunctionArn': 'test-arn:10',
'Events': [],
'Filter': {
'Key': {
'FilterRules': [{
'Name': 'Prefix',
'Value': ''
}, {
'Name': 'Suffix',
'Value': ''
}]
}
}
})
config_from_params = Config.from_params(**{
'event_name': 'test-id',
'lambda_function_arn': 'test-arn',
'lambda_version': 10,
'events': [],
'prefix': '',
'suffix': ''
})
assert config.raw == config_from_params.raw
assert config == config_from_params
class TestModule(ModuleTestCase):
def test_module_fail_when_required_args_missing(self):
with pytest.raises(AnsibleFailJson):
set_module_args({})
s3_bucket_notification.main()
@patch('ansible.modules.cloud.amazon.s3_bucket_notification.AnsibleAWSModule.client')
def test_add_s3_bucket_notification(self, aws_client):
aws_client.return_value.get_bucket_notification_configuration.return_value = {
'LambdaFunctionConfigurations': []
}
set_module_args({
'region': 'us-east-2',
'lambda_function_arn': 'test-lambda-arn',
'bucket_name': 'test-lambda',
'event_name': 'test-id',
'events': ['s3:ObjectCreated:*', 's3:ObjectRemoved:*'],
'state': 'present',
'prefix': '/images',
'suffix': '.jpg'
})
with pytest.raises(AnsibleExitJson) as context:
s3_bucket_notification.main()
result = context.value.args[0]
assert result['changed'] is True
assert aws_client.return_value.get_bucket_notification_configuration.call_count == 1
aws_client.return_value.put_bucket_notification_configuration.assert_called_with(
Bucket='test-lambda',
NotificationConfiguration={
'LambdaFunctionConfigurations': [{
'Id': 'test-id',
'LambdaFunctionArn': 'test-lambda-arn',
'Events': ['s3:ObjectCreated:*', 's3:ObjectRemoved:*'],
'Filter': {
'Key': {
'FilterRules': [{
'Name': 'Prefix',
'Value': '/images'
}, {
'Name': 'Suffix',
'Value': '.jpg'
}]
}
}
}]
})