#!/usr/bin/python # Copyright (c) 2017 Ansible Project # GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) ANSIBLE_METADATA = {'status': ['preview'], 'supported_by': 'community', 'metadata_version': '1.1'} 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.5" 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 = ''' ''' from ansible.module_utils.aws.core import AnsibleAWSModule from ansible.module_utils.ec2 import boto3_conn, ec2_argument_spec, get_aws_connection_info try: import botocore except ImportError: pass # handled by AnsibleAWSModule 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, botocore.exceptions.BotoCoreError) as e: module.fail_json_aws(e, msg="Failed to get Lifecycle Hook") 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, botocore.exceptions.BotoCoreError) as e: module.fail_json_aws(e, msg="Failed to create LifecycleHook") 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, botocore.exceptions.BotoCoreError) as e: module.fail_json_aws(e, msg="Failed to get Lifecycle Hooks") 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, botocore.exceptions.BotoCoreError) as e: module.fail_json_aws(e, msg="Failed to delete LifecycleHook") 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 = AnsibleAWSModule(argument_spec=argument_spec, required_if=[['state', 'present', ['transition']]]) state = module.params.get('state') region, ec2_url, aws_connect_params = get_aws_connection_info(module, boto3=True) connection = boto3_conn(module, conn_type='client', resource='autoscaling', region=region, endpoint=ec2_url, **aws_connect_params) changed = False if state == 'present': changed = create_lifecycle_hook(connection, module) elif state == 'absent': changed = delete_lifecycle_hook(connection, module) module.exit_json(changed=changed) if __name__ == '__main__': main()