diff --git a/lib/ansible/modules/cloud/amazon/ec2_asg_lifecycle_hook.py b/lib/ansible/modules/cloud/amazon/ec2_asg_lifecycle_hook.py
new file mode 100644
index 00000000000..1bedcf40a34
--- /dev/null
+++ b/lib/ansible/modules/cloud/amazon/ec2_asg_lifecycle_hook.py
@@ -0,0 +1,285 @@
+#!/usr/bin/python
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see .
+ANSIBLE_METADATA = {'status': ['preview'],
+ 'supported_by': 'community',
+ 'metadata_version': '1.0'}
+
+DOCUMENTATION = """
+---
+module: ec2_asg_lifecycle_hook
+short_description: Create, delete or update AWS ASG Lifecycle Hooks.
+description:
+ - When no given Hook found, will create one.
+ - In case Hook found, but provided parameters are differes, will update existing Hook.
+ - In case state=absent and Hook exists, will delete it.
+version_added: "2.4"
+author: "Igor (Tsigankov) Eyrich (@tsiganenok) "
+options:
+ state:
+ description:
+ - Create or delete Lifecycle Hook. Present updates existing one or creates if not found.
+ required: false
+ choices: ['present', 'absent']
+ default: present
+ lifecycle_hook_name:
+ description:
+ - The name of the lifecycle hook.
+ required: true
+ autoscaling_group_name:
+ description:
+ - The name of the Auto Scaling group to which you want to assign the lifecycle hook.
+ required: true
+ transition:
+ description:
+ - The instance state to which you want to attach the lifecycle hook.
+ required: true
+ choices: ['autoscaling:EC2_INSTANCE_TERMINATING', 'autoscaling:EC2_INSTANCE_LAUNCHING']
+ role_arn:
+ description:
+ - The ARN of the IAM role that allows the Auto Scaling group to publish to the specified notification target.
+ required: false
+ notification_target_arn:
+ description:
+ - The ARN of the notification target that Auto Scaling will use to notify you when an
+ instance is in the transition state for the lifecycle hook.
+ This target can be either an SQS queue or an SNS topic. If you specify an empty string,
+ this overrides the current ARN.
+ required: false
+ notification_meta_data:
+ description:
+ - Contains additional information that you want to include any time Auto Scaling sends a message to the notification target.
+ required: false
+ heartbeat_timeout:
+ description:
+ - The amount of time, in seconds, that can elapse before the lifecycle hook times out.
+ When the lifecycle hook times out, Auto Scaling performs the default action.
+ You can prevent the lifecycle hook from timing out by calling RecordLifecycleActionHeartbeat.
+ required: false
+ default: 3600 (1 hour)
+ default_result:
+ description:
+ - Defines the action the Auto Scaling group should take when the lifecycle hook timeout
+ elapses or if an unexpected failure occurs. This parameter can be either CONTINUE or ABANDON.
+ required: false
+ choices: ['ABANDON', 'CONTINUE']
+ default: ABANDON
+extends_documentation_fragment:
+ - aws
+ - ec2
+requirements: [ boto3>=1.4.4 ]
+
+"""
+
+EXAMPLES = '''
+# Create / Update lifecycle hook
+- ec2_asg_lifecycle_hook:
+ region: eu-central-1
+ state: present
+ autoscaling_group_name: example
+ lifecycle_hook_name: example
+ transition: autoscaling:EC2_INSTANCE_LAUNCHING
+ heartbeat_timeout: 7000
+ default_result: ABANDON
+
+# Delete lifecycle hook
+- ec2_asg_lifecycle_hook:
+ region: eu-central-1
+ state: absent
+ autoscaling_group_name: example
+ lifecycle_hook_name: example
+
+'''
+
+RETURN = '''
+
+'''
+
+import traceback
+from ansible.module_utils.basic import AnsibleModule
+from ansible.module_utils.ec2 import (boto3_conn, ec2_argument_spec, get_aws_connection_info, HAS_BOTO3)
+
+try:
+ import botocore
+ HAS_BOTOCORE = True
+except ImportError:
+ HAS_BOTOCORE = False
+
+try:
+ import boto3
+except ImportError:
+ # will be caught by imported HAS_BOTO3
+ pass
+
+
+def create_lifecycle_hook(connection, module):
+ changed = False
+
+ lch_name = module.params.get('lifecycle_hook_name')
+ asg_name = module.params.get('autoscaling_group_name')
+ transition = module.params.get('transition')
+ role_arn = module.params.get('role_arn')
+ notification_target_arn = module.params.get('notification_target_arn')
+ notification_meta_data = module.params.get('notification_meta_data')
+ heartbeat_timeout = module.params.get('heartbeat_timeout')
+ default_result = module.params.get('default_result')
+
+ lch_params = {
+ 'LifecycleHookName': lch_name,
+ 'AutoScalingGroupName': asg_name,
+ 'LifecycleTransition': transition
+ }
+
+ if role_arn:
+ lch_params['RoleARN'] = role_arn
+
+ if notification_target_arn:
+ lch_params['NotificationTargetARN'] = notification_target_arn
+
+ if notification_meta_data:
+ lch_params['NotificationMetadata'] = notification_meta_data
+
+ if heartbeat_timeout:
+ lch_params['HeartbeatTimeout'] = heartbeat_timeout
+
+ if default_result:
+ lch_params['DefaultResult'] = default_result
+
+ try:
+ existing_hook = connection.describe_lifecycle_hooks(
+ AutoScalingGroupName=asg_name,
+ LifecycleHookNames=[lch_name]
+ )['LifecycleHooks']
+ except botocore.exceptions.ClientError as e:
+ module.fail_json(msg="Failed to get Lifecycle Hook %s" % str(e), exception=traceback.format_exc(e))
+
+ if not existing_hook:
+ changed = True
+ else:
+ # GlobalTimeout is not configurable, but exists in response.
+ # Removing it helps to compare both dicts in order to understand
+ # what changes were done.
+ del(existing_hook[0]['GlobalTimeout'])
+ added, removed, modified, same = dict_compare(lch_params, existing_hook[0])
+ if added or removed or modified:
+ changed = True
+
+ if changed:
+ try:
+ connection.put_lifecycle_hook(**lch_params)
+ except botocore.exceptions.ClientError as e:
+ module.fail_json(msg="Failed to create LifecycleHook %s" % str(e), exception=traceback.format_exc(e))
+
+ return(changed)
+
+
+def dict_compare(d1, d2):
+ d1_keys = set(d1.keys())
+ d2_keys = set(d2.keys())
+ intersect_keys = d1_keys.intersection(d2_keys)
+ added = d1_keys - d2_keys
+ removed = d2_keys - d1_keys
+ modified = False
+ for key in d1:
+ if d1[key] != d2[key]:
+ modified = True
+ break
+
+ same = set(o for o in intersect_keys if d1[o] == d2[o])
+ return added, removed, modified, same
+
+
+def delete_lifecycle_hook(connection, module):
+ changed = False
+
+ lch_name = module.params.get('lifecycle_hook_name')
+ asg_name = module.params.get('autoscaling_group_name')
+
+ try:
+ all_hooks = connection.describe_lifecycle_hooks(
+ AutoScalingGroupName=asg_name
+ )
+ except botocore.exceptions.ClientError as e:
+ module.fail_json(msg="Failed to get Lifecycle Hooks %s" % str(e), exception=traceback.format_exc(e))
+
+ for hook in all_hooks['LifecycleHooks']:
+ if hook['LifecycleHookName'] == lch_name:
+ lch_params = {
+ 'LifecycleHookName': lch_name,
+ 'AutoScalingGroupName': asg_name
+ }
+
+ try:
+ connection.delete_lifecycle_hook(**lch_params)
+ changed = True
+ except botocore.exceptions.ClientError as e:
+ module.fail_json(msg="Failed to delete LifecycleHook %s" % str(e), exception=traceback.format_exc(e))
+ else:
+ pass
+
+ return(changed)
+
+
+def main():
+ argument_spec = ec2_argument_spec()
+ argument_spec.update(
+ dict(
+ autoscaling_group_name=dict(required=True, type='str'),
+ lifecycle_hook_name=dict(required=True, type='str'),
+ transition=dict(type='str', choices=['autoscaling:EC2_INSTANCE_TERMINATING', 'autoscaling:EC2_INSTANCE_LAUNCHING']),
+ role_arn=dict(type='str'),
+ notification_target_arn=dict(type='str'),
+ notification_meta_data=dict(type='str'),
+ heartbeat_timeout=dict(type='int'),
+ default_result=dict(default='ABANDON', choices=['ABANDON', 'CONTINUE']),
+ state=dict(default='present', choices=['present', 'absent'])
+ )
+ )
+
+ module = AnsibleModule(argument_spec=argument_spec)
+ state = module.params.get('state')
+
+ if not HAS_BOTOCORE:
+ module.fail_json(msg='botocore is required for this module')
+
+ if not HAS_BOTO3:
+ module.fail_json(msg='boto3 is required for this module')
+
+ region, ec2_url, aws_connect_params = get_aws_connection_info(module, boto3=True)
+
+ if not region:
+ module.fail_json(msg="region parameter is required")
+
+ try:
+ connection = boto3_conn(module, conn_type='client', resource='autoscaling', region=region, endpoint=ec2_url, **aws_connect_params)
+ if not connection:
+ module.fail_json(msg="failed to connect to AWS for the given region: %s" % str(region))
+ except botocore.exceptions.NoCredentialsError as e:
+ module.fail_json(msg=str(e))
+
+ changed = create_changed = replace_changed = False
+
+ if state == 'present':
+ if not module.params.get('transition'):
+ module.fail_json(msg="transition parameter is required")
+
+ changed = create_lifecycle_hook(connection, module)
+ elif state == 'absent':
+ changed = delete_lifecycle_hook(connection, module)
+
+ module.exit_json(changed=changed)
+
+if __name__ == '__main__':
+ main()