Add lambda_bucket_event module (#58059)
This commit is contained in:
parent
1c1da3c11d
commit
42073b6331
8 changed files with 879 additions and 1 deletions
|
@ -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": [
|
||||
|
|
267
lib/ansible/modules/cloud/amazon/s3_bucket_notification.py
Normal file
267
lib/ansible/modules/cloud/amazon/s3_bucket_notification.py
Normal 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()
|
2
test/integration/targets/s3_bucket_notification/aliases
Normal file
2
test/integration/targets/s3_bucket_notification/aliases
Normal file
|
@ -0,0 +1,2 @@
|
|||
cloud/aws
|
||||
shippable/aws/group2
|
|
@ -0,0 +1,3 @@
|
|||
---
|
||||
# defaults file for aws_lambda test
|
||||
lambda_function_name: '{{resource_prefix}}'
|
|
@ -0,0 +1,8 @@
|
|||
import json
|
||||
|
||||
|
||||
def lambda_handler(event, context):
|
||||
return {
|
||||
'statusCode': 200,
|
||||
'body': json.dumps('Hello from Lambda!')
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
dependencies:
|
||||
- prepare_tests
|
||||
- setup_ec2
|
335
test/integration/targets/s3_bucket_notification/tasks/main.yml
Normal file
335
test/integration/targets/s3_bucket_notification/tasks/main.yml
Normal 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")'
|
258
test/units/modules/cloud/amazon/test_s3_bucket_notification.py
Normal file
258
test/units/modules/cloud/amazon/test_s3_bucket_notification.py
Normal 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'
|
||||
}]
|
||||
}
|
||||
}
|
||||
}]
|
||||
})
|
Loading…
Reference in a new issue