Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

cloudwatchevent_rule - Add argspec validation for targets #1355

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
minor_changes:
- cloudwatchevent_rule - Added ``input_paths_map`` and ``input_template`` parameters to support ``input_transformer`` on CloudWatch event rule (https://github.com/ansible-collections/community.aws/pull/623).
- cloudwatchevent_rule - Added ``targets.input_transformer.input_paths_map`` and ``targets.input_transformer.input_template`` parameters to
support configuring on CloudWatch event rule input transformation (https://github.com/ansible-collections/community.aws/pull/623).
- cloudwatchevent_rule - Applied validation of ``targets`` arguments (https://github.com/ansible-collections/community.aws/issues/201).
122 changes: 73 additions & 49 deletions plugins/modules/cloudwatchevent_rule.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,10 @@
type: str
event_pattern:
description:
- A string pattern (in valid JSON format) that is used to match against
incoming events to determine if the rule should be triggered.
- A string pattern that is used to match against incoming events to determine if the rule
should be triggered.
required: false
type: str
type: json
state:
description:
- Whether the rule is present (and enabled), disabled, or absent.
Expand Down Expand Up @@ -78,34 +78,34 @@
type: str
description: The ARN of the IAM role to be used for this target when the rule is triggered.
input:
type: str
type: json
description:
- A JSON object that will override the event data when passed to the target.
- If neither I(input) nor I(input_path) nor I(input_paths_map) nor I(input_template)
- A JSON object that will override the event data passed to the target.
- If neither I(input) nor I(input_path) nor I(input_transformer)
is specified, then the entire event is passed to the target in JSON form.
input_path:
type: str
description:
- A JSONPath string (e.g. C($.detail)) that specifies the part of the event data to be
passed to the target.
- If neither I(input) nor I(input_path) nor I(input_paths_map) nor I(input_template)
- If neither I(input) nor I(input_path) nor I(input_transformer)
is specified, then the entire event is passed to the target in JSON form.
input_paths_map:
input_transformer:
type: dict
version_added: 4.1.0
description:
- A dict that specifies the transformation of the event data to
custom input parameters.
- If neither I(input) nor I(input_path) nor I(input_paths_map) nor I(input_template)
is specified, then the entire event is passed to the target in JSON form.
input_template:
type: str
- Settings to support providing custom input to a target based on certain event data.
version_added: 4.1.0
description:
- A string that templates the values input_paths_map extracted from the event data.
It is used to produce the output you want to be sent to the target.
- If neither I(input) nor I(input_path) nor I(input_paths_map) nor I(input_template)
is specified, then the entire event is passed to the target in JSON form.
suboptions:
input_paths_map:
type: dict
description:
- A dict that specifies the transformation of the event data to
custom input parameters.
input_template:
type: json
description:
- A string that templates the values input_paths_map extracted from the event data.
It is used to produce the output you want to be sent to the target.
ecs_parameters:
type: dict
description:
Expand All @@ -114,6 +114,7 @@
task_definition_arn:
type: str
description: The full ARN of the task definition.
required: true
task_count:
type: int
description: The number of tasks to create based on I(task_definition).
Expand Down Expand Up @@ -147,10 +148,11 @@
targets:
- id: MyTargetSnsTopic
arn: arn:aws:sns:us-east-1:123456789012:MySNSTopic
input_paths_map:
instance: "$.detail.instance-id"
state: "$.detail.state"
input_template: "<instance> is in state <state>"
input_transformer:
input_paths_map:
instance: "$.detail.instance-id"
state: "$.detail.state"
input_template: "<instance> is in state <state>"

- community.aws.cloudwatchevent_rule:
name: MyCronTask
Expand All @@ -175,15 +177,28 @@
sample: "[{ 'arn': 'arn:aws:lambda:us-east-1:123456789012:function:MyFunction', 'id': 'MyTargetId' }]"
'''

import json

try:
import botocore
except ImportError:
pass # handled by AnsibleAWSModule

from ansible.module_utils.common.dict_transformations import camel_dict_to_snake_dict
from ansible.module_utils.common.dict_transformations import snake_dict_to_camel_dict

from ansible_collections.amazon.aws.plugins.module_utils.core import AnsibleAWSModule
from ansible_collections.amazon.aws.plugins.module_utils.core import is_boto3_error_code
from ansible_collections.amazon.aws.plugins.module_utils.transformation import scrub_none_parameters


def _format_json(json_string):
# When passed a simple string, Ansible doesn't quote it to ensure it's a *quoted* string
try:
json.loads(json_string)
return json_string
except json.decoder.JSONDecodeError:
return str(json.dumps(json_string))


class CloudWatchEventRule(object):
Expand Down Expand Up @@ -307,29 +322,14 @@ def _targets_request(self, targets):
"""Formats each target for the request"""
targets_request = []
for target in targets:
target_request = {
'Id': target['id'],
'Arn': target['arn']
}
if 'input' in target:
target_request['Input'] = target['input']
if 'input_path' in target:
target_request['InputPath'] = target['input_path']
if 'input_paths_map' in target or 'input_template' in target:
target_request['InputTransformer'] = {}
target_request['InputTransformer']['InputPathsMap'] = target['input_paths_map']
target_request['InputTransformer']['InputTemplate'] = '"{0}"'.format(
target['input_template']
)
if 'role_arn' in target:
target_request['RoleArn'] = target['role_arn']
if 'ecs_parameters' in target:
target_request['EcsParameters'] = {}
ecs_parameters = target['ecs_parameters']
if 'task_definition_arn' in target['ecs_parameters']:
target_request['EcsParameters']['TaskDefinitionArn'] = ecs_parameters['task_definition_arn']
if 'task_count' in target['ecs_parameters']:
target_request['EcsParameters']['TaskCount'] = ecs_parameters['task_count']
target_request = scrub_none_parameters(snake_dict_to_camel_dict(target, True))
if target_request.get('Input', None):
target_request['Input'] = _format_json(target_request['Input'])
if target_request.get('InputTransformer', None):
if target_request.get('InputTransformer').get('InputTemplate', None):
target_request['InputTransformer']['InputTemplate'] = _format_json(target_request['InputTransformer']['InputTemplate'])
if target_request.get('InputTransformer').get('InputPathsMap', None):
target_request['InputTransformer']['InputPathsMap'] = target['input_transformer']['input_paths_map']
targets_request.append(target_request)
return targets_request

Expand Down Expand Up @@ -450,15 +450,39 @@ def _remote_state(self):


def main():
target_args = dict(
type='list', elements='dict', default=[],
options=dict(
id=dict(type='str', required=True),
arn=dict(type='str', required=True),
role_arn=dict(type='str'),
input=dict(type='json'),
input_path=dict(type='str'),
input_transformer=dict(
type='dict',
options=dict(
input_paths_map=dict(type='dict'),
input_template=dict(type='json'),
),
),
ecs_parameters=dict(
type='dict',
options=dict(
task_definition_arn=dict(type='str', required=True),
task_count=dict(type='int'),
),
),
),
)
argument_spec = dict(
name=dict(required=True),
schedule_expression=dict(),
event_pattern=dict(),
event_pattern=dict(type='json'),
state=dict(choices=['present', 'disabled', 'absent'],
default='present'),
description=dict(),
role_arn=dict(),
targets=dict(type='list', default=[], elements='dict'),
targets=target_args,
)
module = AnsibleAWSModule(argument_spec=argument_spec)

Expand Down
10 changes: 5 additions & 5 deletions tests/integration/targets/cloudwatchevent_rule/defaults/main.yml
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
---
name_pattern: "cloudwatch_event_rule"
unique_id: "{{ tiny_prefix }}"
name_pattern: "cloudwatch_event_rule-{{ tiny_prefix }}"

test_event_names:
- "{{ name_pattern }}-{{ unique_id }}-1"
- "{{ name_pattern }}-{{ unique_id }}-2"
- "{{ name_pattern }}-1"
- "{{ name_pattern }}-2"

input_transformer_event_name: "{{ name_pattern }}-{{ unique_id }}-3"
input_transformer_event_name: "{{ name_pattern }}-3"
input_event_name: "{{ name_pattern }}-4"
42 changes: 34 additions & 8 deletions tests/integration/targets/cloudwatchevent_rule/tasks/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,12 @@
register: event_rules_classic_output
loop: "{{ test_event_names }}"

- name: Assert that classic event rules were created
assert:
that:
- event_rules_classic_output.changed
- event_rules_classic_output.msg == "All items completed"

- name: Create cloudwatch event rule with input transformer
cloudwatchevent_rule:
name: "{{ input_transformer_event_name }}"
Expand All @@ -34,17 +40,34 @@
targets:
- id: "{{ sns_topic_output.sns_topic.name }}"
arn: "{{ sns_topic_output.sns_topic.topic_arn }}"
input_paths_map:
instance: "$.detail.instance-id"
state: "$.detail.state"
input_template: "<instance> is in state <state>"
input_transformer:
input_paths_map:
instance: "$.detail.instance-id"
state: "$.detail.state"
input_template: "<instance> is in state <state>"
register: event_rule_input_transformer_output

- name: Assert that classic event rules were created
- name: Assert that input transformer event rule was created
assert:
that:
- event_rules_classic_output.changed
- event_rules_classic_output.msg == "All items completed"
- event_rule_input_transformer_output.changed

- name: Create cloudwatch event rule with inputs
cloudwatchevent_rule:
name: "{{ input_event_name }}"
description: "Event rule with input configuration"
state: present
event_pattern: '{"source":["aws.ec2"],"detail-type":["EC2 Instance State-change Notification"],"detail":{"state":["pending"]}}'
targets:
- id: "{{ sns_topic_output.sns_topic.name }}"
arn: "{{ sns_topic_output.sns_topic.topic_arn }}"
input: 'Hello World'
- id: "{{ sns_topic_output.sns_topic.name }}2"
arn: "{{ sns_topic_output.sns_topic.topic_arn }}"
input:
start: 'Hello World'
end: 'Goodbye oh cruel World'
register: event_rule_input_transformer_output

- name: Assert that input transformer event rule was created
assert:
Expand All @@ -61,8 +84,11 @@

- name: Delete input transformer CloudWatch event rules
cloudwatchevent_rule:
name: "{{ input_transformer_event_name }}"
name: "{{ item }}"
state: absent
loop:
- "{{ input_transformer_event_name }}"
- "{{ input_event_name }}"

- name: Delete SNS topic
sns_topic:
Expand Down