def check_for_update(connection, module_params, vpn_connection_id): """ Determines if there are any tags or routes that need to be updated. Ensures non-modifiable attributes aren't expected to change. """ tags = module_params.get('tags') routes = module_params.get('routes') purge_tags = module_params.get('purge_tags') purge_routes = module_params.get('purge_routes') vpn_connection = find_connection(connection, module_params, vpn_connection_id=vpn_connection_id) current_attrs = ec2_utils.camel_dict_to_snake_dict(vpn_connection) # Initialize changes dict changes = {'tags_to_add': [], 'tags_to_remove': [], 'routes_to_add': [], 'routes_to_remove': []} # Get changes to tags if 'tags' in current_attrs: current_tags = ec2_utils.boto3_tag_list_to_ansible_dict(current_attrs['tags'], u'key', u'value') tags_to_add, changes['tags_to_remove'] = ec2_utils.compare_aws_tags(current_tags, tags, purge_tags) changes['tags_to_add'] = ec2_utils.ansible_dict_to_boto3_tag_list(tags_to_add) elif tags: current_tags = {} tags_to_add, changes['tags_to_remove'] = ec2_utils.compare_aws_tags(current_tags, tags, purge_tags) changes['tags_to_add'] = ec2_utils.ansible_dict_to_boto3_tag_list(tags_to_add) # Get changes to routes if 'Routes' in vpn_connection: current_routes = [route['DestinationCidrBlock'] for route in vpn_connection['Routes']] if purge_routes: changes['routes_to_remove'] = [old_route for old_route in current_routes if old_route not in routes] changes['routes_to_add'] = [new_route for new_route in routes if new_route not in current_routes] # Check if nonmodifiable attributes are attempted to be modified for attribute in current_attrs: if attribute in ("tags", "routes", "state"): continue elif attribute == 'options': will_be = module_params.get('static_only', None) is_now = bool(current_attrs[attribute]['static_routes_only']) attribute = 'static_only' elif attribute == 'type': will_be = module_params.get("connection_type", None) is_now = current_attrs[attribute] else: is_now = current_attrs[attribute] will_be = module_params.get(attribute, None) if will_be is not None and to_text(will_be) != to_text(is_now): raise VPNConnectionException(msg="You cannot modify {0}, the current value of which is {1}. Modifiable VPN " "connection attributes are tags and routes. The value you tried to change it to " "is {2}.".format(attribute, is_now, will_be)) return changes
def update_vpc_tags(connection, module, vpc_id, tags, name): if tags is None: tags = dict() tags.update({'Name': name}) try: current_tags = dict((t['Key'], t['Value']) for t in connection.describe_tags(Filters=[{'Name': 'resource-id', 'Values': [vpc_id]}])['Tags']) if tags != current_tags: if not module.check_mode: tags = ansible_dict_to_boto3_tag_list(tags) vpc_obj = AWSRetry.backoff( delay=1, tries=5, catch_extra_error_codes=['InvalidVpcID.NotFound'], )(connection.create_tags)(Resources=[vpc_id], Tags=tags) # Wait for tags to be updated expected_tags = boto3_tag_list_to_ansible_dict(tags) filters = [{'Name': 'tag:{0}'.format(key), 'Values': [value]} for key, value in expected_tags.items()] connection.get_waiter('vpc_available').wait(VpcIds=[vpc_id], Filters=filters) return True else: return False except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: module.fail_json_aws(e, msg="Failed to update tags")
def ensure_tags(conn, module, subnet, tags, add_only, check_mode): try: cur_tags = subnet['tags'] to_delete = dict((k, cur_tags[k]) for k in cur_tags if k not in tags) if to_delete and not add_only and not check_mode: conn.delete_tags(Resources=[subnet['id']], Tags=ansible_dict_to_boto3_tag_list(to_delete)) to_add = dict((k, tags[k]) for k in tags if k not in cur_tags or cur_tags[k] != tags[k]) if to_add and not check_mode: conn.create_tags(Resources=[subnet['id']], Tags=ansible_dict_to_boto3_tag_list(to_add)) except botocore.exceptions.ClientError as e: if e.response['Error']['Code'] != "DryRunOperation": module.fail_json(msg=e.message, exception=traceback.format_exc(), **camel_dict_to_snake_dict(e.response))
def update_tags(module, connection, group, tags): changed = False existing_tags = connection.list_tags_for_resource(ResourceName=group['DBParameterGroupArn'])['TagList'] to_update, to_delete = compare_aws_tags(boto3_tag_list_to_ansible_dict(existing_tags), tags, module.params['purge_tags']) if to_update: try: connection.add_tags_to_resource(ResourceName=group['DBParameterGroupArn'], Tags=ansible_dict_to_boto3_tag_list(to_update)) changed = True except botocore.exceptions.ClientError as e: module.fail_json(msg="Couldn't add tags to parameter group: %s" % str(e), exception=traceback.format_exc(), **camel_dict_to_snake_dict(e.response)) except botocore.exceptions.ParamValidationError as e: # Usually a tag value has been passed as an int or bool, needs to be a string # The AWS exception message is reasonably ok for this purpose module.fail_json(msg="Couldn't add tags to parameter group: %s." % str(e), exception=traceback.format_exc()) if to_delete: try: connection.remove_tags_from_resource(ResourceName=group['DBParameterGroupArn'], TagKeys=to_delete) changed = True except botocore.exceptions.ClientError as e: module.fail_json(msg="Couldn't remove tags from parameter group: %s" % str(e), exception=traceback.format_exc(), **camel_dict_to_snake_dict(e.response)) return changed
def __init__(self, connection, module): self.connection = connection self.module = module self.changed = False self.new_load_balancer = False self.scheme = module.params.get("scheme") self.name = module.params.get("name") self.subnet_mappings = module.params.get("subnet_mappings") self.subnets = module.params.get("subnets") self.deletion_protection = module.params.get("deletion_protection") self.wait = module.params.get("wait") if module.params.get("tags") is not None: self.tags = ansible_dict_to_boto3_tag_list(module.params.get("tags")) else: self.tags = None self.purge_tags = module.params.get("purge_tags") self.elb = get_elb(connection, module, self.name) if self.elb is not None: self.elb_attributes = self.get_elb_attributes() self.elb['tags'] = self.get_elb_tags() else: self.elb_attributes = None
def ensure_tags(connection=None, module=None, resource_id=None, tags=None, purge_tags=None, check_mode=None): try: cur_tags = describe_tags_with_backoff(connection, resource_id) except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: module.fail_json_aws(e, msg='Unable to list tags for VPC') to_add, to_delete = compare_aws_tags(cur_tags, tags, purge_tags) if not to_add and not to_delete: return {'changed': False, 'tags': cur_tags} if check_mode: if not purge_tags: tags = cur_tags.update(tags) return {'changed': True, 'tags': tags} if to_delete: try: connection.delete_tags(Resources=[resource_id], Tags=[{'Key': k} for k in to_delete]) except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: module.fail_json_aws(e, msg="Couldn't delete tags") if to_add: try: connection.create_tags(Resources=[resource_id], Tags=ansible_dict_to_boto3_tag_list(to_add)) except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: module.fail_json_aws(e, msg="Couldn't create tags") try: latest_tags = describe_tags_with_backoff(connection, resource_id) except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: module.fail_json_aws(e, msg='Unable to list tags for VPC') return {'changed': True, 'tags': latest_tags}
def ensure_present(module, connection): groupname = module.params['name'] tags = module.params.get('tags') changed = False errors = [] try: response = connection.describe_db_parameter_groups(DBParameterGroupName=groupname) except botocore.exceptions.ClientError as e: if e.response['Error']['Code'] == 'DBParameterGroupNotFound': response = None else: module.fail_json(msg="Couldn't access parameter group information: %s" % str(e), exception=traceback.format_exc(), **camel_dict_to_snake_dict(e.response)) if not response: params = dict(DBParameterGroupName=groupname, DBParameterGroupFamily=module.params['engine'], Description=module.params['description']) if tags: params['Tags'] = ansible_dict_to_boto3_tag_list(tags) try: response = connection.create_db_parameter_group(**params) changed = True except botocore.exceptions.ClientError as e: module.fail_json(msg="Couldn't create parameter group: %s" % str(e), exception=traceback.format_exc(), **camel_dict_to_snake_dict(e.response)) else: group = response['DBParameterGroups'][0] if tags: changed = update_tags(module, connection, group, tags) if module.params.get('params'): params_changed, errors = update_parameters(module, connection) changed = changed or params_changed try: response = connection.describe_db_parameter_groups(DBParameterGroupName=groupname) group = camel_dict_to_snake_dict(response['DBParameterGroups'][0]) except botocore.exceptions.ClientError as e: module.fail_json(msg="Couldn't obtain parameter group information: %s" % str(e), exception=traceback.format_exc(), **camel_dict_to_snake_dict(e.response)) try: tags = connection.list_tags_for_resource(ResourceName=group['db_parameter_group_arn'])['TagList'] except botocore.exceptions.ClientError as e: module.fail_json(msg="Couldn't obtain parameter group tags: %s" % str(e), exception=traceback.format_exc(), **camel_dict_to_snake_dict(e.response)) group['tags'] = boto3_tag_list_to_ansible_dict(tags) module.exit_json(changed=changed, errors=errors, **group)
def tag_trail(module, client, tags, trail_arn, curr_tags=None, dry_run=False): """ Creates, updates, removes tags on a CloudTrail resource module : AnsibleModule object client : boto3 client connection object tags : Dict of tags converted from ansible_dict to boto3 list of dicts trail_arn : The ARN of the CloudTrail to operate on curr_tags : Dict of the current tags on resource, if any dry_run : true/false to determine if changes will be made if needed """ adds = [] removes = [] updates = [] changed = False if curr_tags is None: # No current tags so just convert all to a tag list adds = ansible_dict_to_boto3_tag_list(tags) else: curr_keys = set(curr_tags.keys()) new_keys = set(tags.keys()) add_keys = new_keys - curr_keys remove_keys = curr_keys - new_keys update_keys = dict() for k in curr_keys.intersection(new_keys): if curr_tags[k] != tags[k]: update_keys.update({k: tags[k]}) adds = get_tag_list(add_keys, tags) removes = get_tag_list(remove_keys, curr_tags) updates = get_tag_list(update_keys, tags) if removes or updates: changed = True if not dry_run: try: client.remove_tags(ResourceId=trail_arn, TagsList=removes + updates) except ClientError as err: module.fail_json(msg=err.message, exception=traceback.format_exc(), **camel_dict_to_snake_dict(err.response)) if updates or adds: changed = True if not dry_run: try: client.add_tags(ResourceId=trail_arn, TagsList=updates + adds) except ClientError as err: module.fail_json(msg=err.message, exception=traceback.format_exc(), **camel_dict_to_snake_dict(err.response)) return changed
def ensure_tags(conn, module, subnet, tags, purge_tags, start_time): changed = False filters = ansible_dict_to_boto3_filter_list({'resource-id': subnet['id'], 'resource-type': 'subnet'}) try: cur_tags = conn.describe_tags(Filters=filters) except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: module.fail_json_aws(e, msg="Couldn't describe tags") to_update, to_delete = compare_aws_tags(boto3_tag_list_to_ansible_dict(cur_tags.get('Tags')), tags, purge_tags) if to_update: try: if not module.check_mode: AWSRetry.exponential_backoff( catch_extra_error_codes=['InvalidSubnetID.NotFound'] )(conn.create_tags)( Resources=[subnet['id']], Tags=ansible_dict_to_boto3_tag_list(to_update) ) changed = True except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: module.fail_json_aws(e, msg="Couldn't create tags") if to_delete: try: if not module.check_mode: tags_list = [] for key in to_delete: tags_list.append({'Key': key}) AWSRetry.exponential_backoff( catch_extra_error_codes=['InvalidSubnetID.NotFound'] )(conn.delete_tags)(Resources=[subnet['id']], Tags=tags_list) changed = True except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: module.fail_json_aws(e, msg="Couldn't delete tags") if module.params['wait'] and not module.check_mode: # Wait for tags to be updated filters = [{'Name': 'tag:{0}'.format(k), 'Values': [v]} for k, v in tags.items()] handle_waiter(conn, module, 'subnet_exists', {'SubnetIds': [subnet['id']], 'Filters': filters}, start_time) return changed
def create_or_update_target_group(connection, module): changed = False new_target_group = False params = dict() params['Name'] = module.params.get("name") params['Protocol'] = module.params.get("protocol").upper() params['Port'] = module.params.get("port") params['VpcId'] = module.params.get("vpc_id") tags = module.params.get("tags") purge_tags = module.params.get("purge_tags") deregistration_delay_timeout = module.params.get("deregistration_delay_timeout") stickiness_enabled = module.params.get("stickiness_enabled") stickiness_lb_cookie_duration = module.params.get("stickiness_lb_cookie_duration") stickiness_type = module.params.get("stickiness_type") # If health check path not None, set health check attributes if module.params.get("health_check_path") is not None: params['HealthCheckPath'] = module.params.get("health_check_path") if module.params.get("health_check_protocol") is not None: params['HealthCheckProtocol'] = module.params.get("health_check_protocol").upper() if module.params.get("health_check_port") is not None: params['HealthCheckPort'] = module.params.get("health_check_port") if module.params.get("health_check_interval") is not None: params['HealthCheckIntervalSeconds'] = module.params.get("health_check_interval") if module.params.get("health_check_timeout") is not None: params['HealthCheckTimeoutSeconds'] = module.params.get("health_check_timeout") if module.params.get("healthy_threshold_count") is not None: params['HealthyThresholdCount'] = module.params.get("healthy_threshold_count") if module.params.get("unhealthy_threshold_count") is not None: params['UnhealthyThresholdCount'] = module.params.get("unhealthy_threshold_count") if module.params.get("successful_response_codes") is not None: params['Matcher'] = {} params['Matcher']['HttpCode'] = module.params.get("successful_response_codes") # Get target group tg = get_target_group(connection, module) if tg: diffs = [param for param in ('Port', 'Protocol', 'VpcId') if tg.get(param) != params.get(param)] if diffs: module.fail_json(msg="Cannot modify %s parameter(s) for a target group" % ", ".join(diffs)) # Target group exists so check health check parameters match what has been passed health_check_params = dict() # If we have no health check path then we have nothing to modify if module.params.get("health_check_path") is not None: # Health check protocol if 'HealthCheckProtocol' in params and tg['HealthCheckProtocol'] != params['HealthCheckProtocol']: health_check_params['HealthCheckProtocol'] = params['HealthCheckProtocol'] # Health check port if 'HealthCheckPort' in params and tg['HealthCheckPort'] != params['HealthCheckPort']: health_check_params['HealthCheckPort'] = params['HealthCheckPort'] # Health check path if 'HealthCheckPath'in params and tg['HealthCheckPath'] != params['HealthCheckPath']: health_check_params['HealthCheckPath'] = params['HealthCheckPath'] # Health check interval if 'HealthCheckIntervalSeconds' in params and tg['HealthCheckIntervalSeconds'] != params['HealthCheckIntervalSeconds']: health_check_params['HealthCheckIntervalSeconds'] = params['HealthCheckIntervalSeconds'] # Health check timeout if 'HealthCheckTimeoutSeconds' in params and tg['HealthCheckTimeoutSeconds'] != params['HealthCheckTimeoutSeconds']: health_check_params['HealthCheckTimeoutSeconds'] = params['HealthCheckTimeoutSeconds'] # Healthy threshold if 'HealthyThresholdCount' in params and tg['HealthyThresholdCount'] != params['HealthyThresholdCount']: health_check_params['HealthyThresholdCount'] = params['HealthyThresholdCount'] # Unhealthy threshold if 'UnhealthyThresholdCount' in params and tg['UnhealthyThresholdCount'] != params['UnhealthyThresholdCount']: health_check_params['UnhealthyThresholdCount'] = params['UnhealthyThresholdCount'] # Matcher (successful response codes) # TODO: required and here? if 'Matcher' in params: current_matcher_list = tg['Matcher']['HttpCode'].split(',') requested_matcher_list = params['Matcher']['HttpCode'].split(',') if set(current_matcher_list) != set(requested_matcher_list): health_check_params['Matcher'] = {} health_check_params['Matcher']['HttpCode'] = ','.join(requested_matcher_list) try: if health_check_params: connection.modify_target_group(TargetGroupArn=tg['TargetGroupArn'], **health_check_params) changed = True except ClientError as e: module.fail_json(msg=e.message, exception=traceback.format_exc(), **camel_dict_to_snake_dict(e.response)) # Do we need to modify targets? if module.params.get("modify_targets"): if module.params.get("targets"): params['Targets'] = module.params.get("targets") # get list of current target instances. I can't see anything like a describe targets in the doco so # describe_target_health seems to be the only way to get them try: current_targets = connection.describe_target_health(TargetGroupArn=tg['TargetGroupArn']) except ClientError as e: module.fail_json(msg=e.message, exception=traceback.format_exc(), **camel_dict_to_snake_dict(e.response)) current_instance_ids = [] for instance in current_targets['TargetHealthDescriptions']: current_instance_ids.append(instance['Target']['Id']) new_instance_ids = [] for instance in params['Targets']: new_instance_ids.append(instance['Id']) add_instances = set(new_instance_ids) - set(current_instance_ids) if add_instances: instances_to_add = [] for target in params['Targets']: if target['Id'] in add_instances: instances_to_add.append({'Id': target['Id'], 'Port': int(target['Port'])}) changed = True try: connection.register_targets(TargetGroupArn=tg['TargetGroupArn'], Targets=instances_to_add) except ClientError as e: module.fail_json(msg=e.message, exception=traceback.format_exc(), **camel_dict_to_snake_dict(e.response)) if module.params.get("wait"): status_achieved, registered_instances = wait_for_status(connection, module, tg['TargetGroupArn'], instances_to_add, 'healthy') if not status_achieved: module.fail_json(msg='Error waiting for target registration - please check the AWS console') remove_instances = set(current_instance_ids) - set(new_instance_ids) if remove_instances: instances_to_remove = [] for target in current_targets['TargetHealthDescriptions']: if target['Target']['Id'] in remove_instances: instances_to_remove.append({'Id': target['Target']['Id'], 'Port': target['Target']['Port']}) changed = True try: connection.deregister_targets(TargetGroupArn=tg['TargetGroupArn'], Targets=instances_to_remove) except ClientError as e: module.fail_json(msg=e.message, exception=traceback.format_exc(), **camel_dict_to_snake_dict(e.response)) if module.params.get("wait"): status_achieved, registered_instances = wait_for_status(connection, module, tg['TargetGroupArn'], instances_to_remove, 'unused') if not status_achieved: module.fail_json(msg='Error waiting for target deregistration - please check the AWS console') else: try: current_targets = connection.describe_target_health(TargetGroupArn=tg['TargetGroupArn']) except ClientError as e: module.fail_json(msg=e.message, exception=traceback.format_exc(), **camel_dict_to_snake_dict(e.response)) current_instances = current_targets['TargetHealthDescriptions'] if current_instances: instances_to_remove = [] for target in current_targets['TargetHealthDescriptions']: instances_to_remove.append({'Id': target['Target']['Id'], 'Port': target['Target']['Port']}) changed = True try: connection.deregister_targets(TargetGroupArn=tg['TargetGroupArn'], Targets=instances_to_remove) except ClientError as e: module.fail_json(msg=e.message, exception=traceback.format_exc(), **camel_dict_to_snake_dict(e.response)) if module.params.get("wait"): status_achieved, registered_instances = wait_for_status(connection, module, tg['TargetGroupArn'], instances_to_remove, 'unused') if not status_achieved: module.fail_json(msg='Error waiting for target deregistration - please check the AWS console') else: try: connection.create_target_group(**params) changed = True new_target_group = True except ClientError as e: module.fail_json(msg=e.message, exception=traceback.format_exc(), **camel_dict_to_snake_dict(e.response)) tg = get_target_group(connection, module) if module.params.get("targets"): params['Targets'] = module.params.get("targets") try: connection.register_targets(TargetGroupArn=tg['TargetGroupArn'], Targets=params['Targets']) except ClientError as e: module.fail_json(msg=e.message, exception=traceback.format_exc(), **camel_dict_to_snake_dict(e.response)) if module.params.get("wait"): status_achieved, registered_instances = wait_for_status(connection, module, tg['TargetGroupArn'], params['Targets'], 'healthy') if not status_achieved: module.fail_json(msg='Error waiting for target registration - please check the AWS console') # Now set target group attributes update_attributes = [] # Get current attributes current_tg_attributes = get_tg_attributes(connection, module, tg['TargetGroupArn']) if deregistration_delay_timeout is not None: if str(deregistration_delay_timeout) != current_tg_attributes['deregistration_delay_timeout_seconds']: update_attributes.append({'Key': 'deregistration_delay.timeout_seconds', 'Value': str(deregistration_delay_timeout)}) if stickiness_enabled is not None: if stickiness_enabled and current_tg_attributes['stickiness_enabled'] != "true": update_attributes.append({'Key': 'stickiness.enabled', 'Value': 'true'}) if stickiness_lb_cookie_duration is not None: if str(stickiness_lb_cookie_duration) != current_tg_attributes['stickiness_lb_cookie_duration_seconds']: update_attributes.append({'Key': 'stickiness.lb_cookie.duration_seconds', 'Value': str(stickiness_lb_cookie_duration)}) if stickiness_type is not None and "stickiness_type" in current_tg_attributes: if stickiness_type != current_tg_attributes['stickiness_type']: update_attributes.append({'Key': 'stickiness.type', 'Value': stickiness_type}) if update_attributes: try: connection.modify_target_group_attributes(TargetGroupArn=tg['TargetGroupArn'], Attributes=update_attributes) changed = True except ClientError as e: # Something went wrong setting attributes. If this target group was created during this task, delete it to leave a consistent state if new_target_group: connection.delete_target_group(TargetGroupArn=tg['TargetGroupArn']) module.fail_json(msg=e.message, exception=traceback.format_exc(), **camel_dict_to_snake_dict(e.response)) # Tags - only need to play with tags if tags parameter has been set to something if tags: # Get tags current_tags = get_target_group_tags(connection, module, tg['TargetGroupArn']) # Delete necessary tags tags_need_modify, tags_to_delete = compare_aws_tags(boto3_tag_list_to_ansible_dict(current_tags), tags, purge_tags) if tags_to_delete: try: connection.remove_tags(ResourceArns=[tg['TargetGroupArn']], TagKeys=tags_to_delete) except ClientError as e: module.fail_json(msg=e.message, exception=traceback.format_exc(), **camel_dict_to_snake_dict(e.response)) changed = True # Add/update tags if tags_need_modify: try: connection.add_tags(ResourceArns=[tg['TargetGroupArn']], Tags=ansible_dict_to_boto3_tag_list(tags_need_modify)) except ClientError as e: module.fail_json(msg=e.message, exception=traceback.format_exc(), **camel_dict_to_snake_dict(e.response)) changed = True # Get the target group again tg = get_target_group(connection, module) # Get the target group attributes again tg.update(get_tg_attributes(connection, module, tg['TargetGroupArn'])) # Convert tg to snake_case snaked_tg = camel_dict_to_snake_dict(tg) snaked_tg['tags'] = boto3_tag_list_to_ansible_dict(get_target_group_tags(connection, module, tg['TargetGroupArn'])) module.exit_json(changed=changed, **snaked_tg)
def converge_file_system(self, name, tags, purge_tags, targets, throughput_mode, provisioned_throughput_in_mibps): """ Change attributes (mount targets and tags) of filesystem by name """ result = False fs_id = self.get_file_system_id(name) if tags is not None: tags_need_modify, tags_to_delete = compare_aws_tags(boto3_tag_list_to_ansible_dict(self.get_tags(FileSystemId=fs_id)), tags, purge_tags) if tags_to_delete: try: self.connection.delete_tags( FileSystemId=fs_id, TagKeys=tags_to_delete ) except ClientError as e: self.module.fail_json(msg="Unable to delete tags: {0}".format(to_native(e)), exception=traceback.format_exc(), **camel_dict_to_snake_dict(e.response)) except BotoCoreError as e: self.module.fail_json(msg="Unable to delete tags: {0}".format(to_native(e)), exception=traceback.format_exc()) result = True if tags_need_modify: try: self.connection.create_tags( FileSystemId=fs_id, Tags=ansible_dict_to_boto3_tag_list(tags_need_modify) ) except ClientError as e: self.module.fail_json(msg="Unable to create tags: {0}".format(to_native(e)), exception=traceback.format_exc(), **camel_dict_to_snake_dict(e.response)) except BotoCoreError as e: self.module.fail_json(msg="Unable to create tags: {0}".format(to_native(e)), exception=traceback.format_exc()) result = True if targets is not None: incomplete_states = [self.STATE_CREATING, self.STATE_DELETING] wait_for( lambda: len(self.get_mount_targets_in_state(fs_id, incomplete_states)), 0 ) current_targets = _index_by_key('SubnetId', self.get_mount_targets(FileSystemId=fs_id)) targets = _index_by_key('SubnetId', targets) targets_to_create, intersection, targets_to_delete = dict_diff(current_targets, targets, True) # To modify mount target it should be deleted and created again changed = [sid for sid in intersection if not targets_equal(['SubnetId', 'IpAddress', 'NetworkInterfaceId'], current_targets[sid], targets[sid])] targets_to_delete = list(targets_to_delete) + changed targets_to_create = list(targets_to_create) + changed if targets_to_delete: for sid in targets_to_delete: self.connection.delete_mount_target( MountTargetId=current_targets[sid]['MountTargetId'] ) wait_for( lambda: len(self.get_mount_targets_in_state(fs_id, incomplete_states)), 0 ) result = True if targets_to_create: for sid in targets_to_create: self.connection.create_mount_target( FileSystemId=fs_id, **targets[sid] ) wait_for( lambda: len(self.get_mount_targets_in_state(fs_id, incomplete_states)), 0, self.wait_timeout ) result = True # If no security groups were passed into the module, then do not change it. security_groups_to_update = [sid for sid in intersection if 'SecurityGroups' in targets[sid] and current_targets[sid]['SecurityGroups'] != targets[sid]['SecurityGroups']] if security_groups_to_update: for sid in security_groups_to_update: self.connection.modify_mount_target_security_groups( MountTargetId=current_targets[sid]['MountTargetId'], SecurityGroups=targets[sid].get('SecurityGroups', None) ) result = True return result
def boto3_tags(self): return ansible_dict_to_boto3_tag_list(self.Tags)
def check_for_update(connection, module_params, vpn_connection_id): """ Determines if there are any tags or routes that need to be updated. Ensures non-modifiable attributes aren't expected to change. """ tags = module_params.get('tags') routes = module_params.get('routes') purge_tags = module_params.get('purge_tags') purge_routes = module_params.get('purge_routes') vpn_connection = find_connection(connection, module_params, vpn_connection_id=vpn_connection_id) current_attrs = camel_dict_to_snake_dict(vpn_connection) # Initialize changes dict changes = { 'tags_to_add': [], 'tags_to_remove': [], 'routes_to_add': [], 'routes_to_remove': [] } # Get changes to tags current_tags = boto3_tag_list_to_ansible_dict( current_attrs.get('tags', []), u'key', u'value') tags_to_add, changes['tags_to_remove'] = compare_aws_tags( current_tags, tags, purge_tags) changes['tags_to_add'] = ansible_dict_to_boto3_tag_list(tags_to_add) # Get changes to routes if 'Routes' in vpn_connection: current_routes = [ route['DestinationCidrBlock'] for route in vpn_connection['Routes'] ] if purge_routes: changes['routes_to_remove'] = [ old_route for old_route in current_routes if old_route not in routes ] changes['routes_to_add'] = [ new_route for new_route in routes if new_route not in current_routes ] # Check if nonmodifiable attributes are attempted to be modified for attribute in current_attrs: if attribute in ("tags", "routes", "state"): continue elif attribute == 'options': will_be = module_params.get('static_only', None) is_now = bool(current_attrs[attribute]['static_routes_only']) attribute = 'static_only' elif attribute == 'type': will_be = module_params.get("connection_type", None) is_now = current_attrs[attribute] else: is_now = current_attrs[attribute] will_be = module_params.get(attribute, None) if will_be is not None and to_text(will_be) != to_text(is_now): raise VPNConnectionException( msg= "You cannot modify {0}, the current value of which is {1}. Modifiable VPN " "connection attributes are tags and routes. The value you tried to change it to " "is {2}.".format(attribute, is_now, will_be)) return changes
def main(): argument_spec = dict( stack_set_name=dict(required=True), description=dict(), wait=dict(type='bool', default=False), wait_timeout=dict(type='int', default=900), state=dict(default='present', choices=['present', 'absent']), parameters=dict(type='dict', default={}), permission_model=dict(type='str', choices=['SERVICE_MANAGED', 'SELF_MANAGED']), auto_deployment=dict( type=dict, default={}, options=dict(enabled=dict(type='bool'), retain_stacks_on_account_removal=dict(type='bool'))), template=dict(type='path'), template_url=dict(), template_body=dict(), capabilities=dict(type='list', elements='str', choices=['CAPABILITY_IAM', 'CAPABILITY_NAMED_IAM']), administration_role_arn=dict( aliases=['admin_role_arn', 'administration_role', 'admin_role']), execution_role_name=dict( aliases=['execution_role', 'exec_role', 'exec_role_name']), tags=dict(type='dict'), ) module = AnsibleAWSModule( argument_spec=argument_spec, mutually_exclusive=[['template_url', 'template', 'template_body']], supports_check_mode=True) if not (module.boto3_at_least('1.14.0') and module.botocore_at_least('1.17.7')): module.fail_json( msg= "Boto3 or botocore version is too low. This module requires at least boto3 1.6 and botocore 1.10.26" ) # Wrap the cloudformation client methods that this module uses with # automatic backoff / retry for throttling error codes jittered_backoff_decorator = AWSRetry.jittered_backoff( retries=10, delay=3, max_delay=30, catch_extra_error_codes=['StackSetNotFound']) cfn = module.client('cloudformation', retry_decorator=jittered_backoff_decorator) existing_stack_set = stack_set_facts(cfn, module.params['stack_set_name']) operation_uuid = to_native(uuid.uuid4()) operation_ids = [] # collect the parameters that are passed to boto3. Keeps us from having so many scalars floating around. stack_params = {} state = module.params['state'] stack_params['StackSetName'] = module.params['stack_set_name'] if module.params.get('description'): stack_params['Description'] = module.params['description'] if module.params.get('capabilities'): stack_params['Capabilities'] = module.params['capabilities'] if module.params['template'] is not None: with open(module.params['template'], 'r') as tpl: stack_params['TemplateBody'] = tpl.read() elif module.params['template_body'] is not None: stack_params['TemplateBody'] = module.params['template_body'] elif module.params['template_url'] is not None: stack_params['TemplateURL'] = module.params['template_url'] else: # no template is provided, but if the stack set exists already, we can use the existing one. if existing_stack_set: stack_params['UsePreviousTemplate'] = True else: module.fail_json( msg= "The Stack Set {0} does not exist, and no template was provided. Provide one of `template`, " "`template_body`, or `template_url`".format( module.params['stack_set_name'])) stack_params['Parameters'] = [] for k, v in module.params['parameters'].items(): if isinstance(v, dict): # set parameter based on a dict to allow additional CFN Parameter Attributes param = dict(ParameterKey=k) if 'value' in v: param['ParameterValue'] = to_native(v['value']) if 'use_previous_value' in v and bool(v['use_previous_value']): param['UsePreviousValue'] = True param.pop('ParameterValue', None) stack_params['Parameters'].append(param) else: # allow default k/v configuration to set a template parameter stack_params['Parameters'].append({ 'ParameterKey': k, 'ParameterValue': str(v) }) if module.params.get('tags') and isinstance(module.params.get('tags'), dict): stack_params['Tags'] = ansible_dict_to_boto3_tag_list( module.params['tags']) if module.params.get('administration_role_arn'): # TODO loosen the semantics here to autodetect the account ID and build the ARN stack_params['AdministrationRoleARN'] = module.params[ 'administration_role_arn'] if module.params.get('execution_role_name'): stack_params['ExecutionRoleName'] = module.params[ 'execution_role_name'] if module.params.get('permission_model'): stack_params['PermissionModel'] = module.params.get('permission_model') if module.params.get('auto_deployment'): param_auto_deployment = {} auto_deployment = module.params.get('auto_deployment') if 'enabled' in auto_deployment.keys(): param_auto_deployment['Enabled'] = auto_deployment['enabled'] if 'retain_stacks_on_account_removal' in auto_deployment.keys(): param_auto_deployment[ 'RetainStacksOnAccountRemoval'] = auto_deployment[ 'retain_stacks_on_account_removal'] stack_params['AutoDeployment'] = param_auto_deployment result = {} if module.check_mode: if state == 'absent' and existing_stack_set: module.exit_json(changed=True, msg='Stack set would be deleted', meta=[]) elif state == 'absent' and not existing_stack_set: module.exit_json(changed=False, msg='Stack set doesn\'t exist', meta=[]) elif state == 'present' and not existing_stack_set: module.exit_json(changed=True, msg='New stack set would be created', meta=[]) elif state == 'present' and existing_stack_set: module.exit_json(changed=True, msg='Existing stack set would be updated', meta=[]) else: # TODO: need to check the template and other settings for correct check mode module.exit_json(changed=False, msg='No changes detected', meta=[]) changed = False if state == 'present': if not existing_stack_set: # on create this parameter has a different name, and cannot be referenced later in the job log stack_params[ 'ClientRequestToken'] = 'Ansible-StackSet-Create-{0}'.format( operation_uuid) changed = True create_stack_set(module, stack_params, cfn) else: stack_params['OperationId'] = 'Ansible-StackSet-Update-{0}'.format( operation_uuid) operation_ids.append(stack_params['OperationId']) changed |= update_stack_set(module, stack_params, cfn) elif state == 'absent': if not existing_stack_set: module.exit_json(msg='Stack set {0} does not exist'.format( module.params['stack_set_name'])) try: cfn.delete_stack_set( StackSetName=module.params['stack_set_name'], ) module.exit_json(msg='Stack set {0} deleted'.format( module.params['stack_set_name'])) except is_boto3_error_code('OperationInProgressException') as e: # pylint: disable=duplicate-except module.fail_json_aws( e, msg= 'Cannot delete stack {0} while there is an operation in progress' .format(module.params['stack_set_name'])) except is_boto3_error_code('StackSetNotEmptyException'): # pylint: disable=duplicate-except try: cfn.delete_stack_set( StackSetName=module.params['stack_set_name'], ) except is_boto3_error_code('StackSetNotEmptyException') as exc: # pylint: disable=duplicate-except module.fail_json_aws( exc, msg= 'Could not purge stacks, or not all accounts/regions were chosen for deletion' ) module.exit_json(changed=True, msg='Stack set {0} deleted'.format( module.params['stack_set_name'])) result.update(**describe_stack_tree( module, stack_params['StackSetName'], operation_ids=operation_ids)) if 'operations' in result.keys(): if any(o['status'] == 'FAILED' for o in result['operations']): module.fail_json(msg="One or more operations failed to execute", **result) module.exit_json(changed=changed, **result)
def main(): argument_spec = dict(cluster_name=dict(required=True), resource=dict(required=False), tags=dict(type='dict'), purge_tags=dict(type='bool', default=False), state=dict(default='present', choices=['present', 'absent']), resource_type=dict(default='cluster', choices=[ 'cluster', 'task', 'service', 'task_definition', 'container' ])) required_if = [('state', 'present', ['tags']), ('state', 'absent', ['tags'])] module = AnsibleAWSModule(argument_spec=argument_spec, required_if=required_if, supports_check_mode=True) resource_type = module.params['resource_type'] cluster_name = module.params['cluster_name'] if resource_type == 'cluster': resource = cluster_name else: resource = module.params['resource'] tags = module.params['tags'] state = module.params['state'] purge_tags = module.params['purge_tags'] result = {'changed': False} ecs = module.client('ecs') resource_arn = get_arn(ecs, module, cluster_name, resource_type, resource) current_tags = get_tags(ecs, module, resource_arn) add_tags, remove = compare_aws_tags(current_tags, tags, purge_tags=purge_tags) remove_tags = {} if state == 'absent': for key in tags: if key in current_tags and (tags[key] is None or current_tags[key] == tags[key]): remove_tags[key] = current_tags[key] for key in remove: remove_tags[key] = current_tags[key] if remove_tags: result['changed'] = True result['removed_tags'] = remove_tags if not module.check_mode: try: ecs.untag_resource(resourceArn=resource_arn, tagKeys=list(remove_tags.keys())) except (BotoCoreError, ClientError) as e: module.fail_json_aws( e, msg='Failed to remove tags {0} from resource {1}'.format( remove_tags, resource)) if state == 'present' and add_tags: result['changed'] = True result['added_tags'] = add_tags current_tags.update(add_tags) if not module.check_mode: try: tags = ansible_dict_to_boto3_tag_list( add_tags, tag_name_key_name='key', tag_value_key_name='value') ecs.tag_resource(resourceArn=resource_arn, tags=tags) except (BotoCoreError, ClientError) as e: module.fail_json_aws( e, msg='Failed to set tags {0} on resource {1}'.format( add_tags, resource)) result['tags'] = get_tags(ecs, module, resource_arn) module.exit_json(**result)
def main(): argument_spec = ec2_argument_spec() argument_spec.update(dict( name=dict(), group_id=dict(), description=dict(), vpc_id=dict(), rules=dict(type='list'), rules_egress=dict(type='list'), state=dict(default='present', type='str', choices=['present', 'absent']), purge_rules=dict(default=True, required=False, type='bool'), purge_rules_egress=dict(default=True, required=False, type='bool'), tags=dict(required=False, type='dict', aliases=['resource_tags']), purge_tags=dict(default=True, required=False, type='bool') ) ) module = AnsibleModule( argument_spec=argument_spec, supports_check_mode=True, required_one_of=[['name', 'group_id']], required_if=[['state', 'present', ['name']]], ) if not HAS_BOTO3: module.fail_json(msg='boto3 required for this module') name = module.params['name'] group_id = module.params['group_id'] description = module.params['description'] vpc_id = module.params['vpc_id'] rules = deduplicate_rules_args(rules_expand_sources(rules_expand_ports(module.params['rules']))) rules_egress = deduplicate_rules_args(rules_expand_sources(rules_expand_ports(module.params['rules_egress']))) state = module.params.get('state') purge_rules = module.params['purge_rules'] purge_rules_egress = module.params['purge_rules_egress'] tags = module.params['tags'] purge_tags = module.params['purge_tags'] if state == 'present' and not description: module.fail_json(msg='Must provide description when state is present.') changed = False region, ec2_url, aws_connect_params = get_aws_connection_info(module, boto3=True) if not region: module.fail_json(msg="The AWS region must be specified as an " "environment variable or in the AWS credentials " "profile.") client = boto3_conn(module, conn_type='client', resource='ec2', endpoint=ec2_url, region=region, **aws_connect_params) group = None groups = dict() security_groups = [] # do get all security groups # find if the group is present try: response = get_security_groups_with_backoff(client) security_groups = response.get('SecurityGroups', []) except botocore.exceptions.NoCredentialsError as e: module.fail_json(msg="Error in describe_security_groups: %s" % "Unable to locate credentials", exception=traceback.format_exc()) except botocore.exceptions.ClientError as e: module.fail_json(msg="Error in describe_security_groups: %s" % e, exception=traceback.format_exc(), **camel_dict_to_snake_dict(e.response)) for sg in security_groups: groups[sg['GroupId']] = sg groupName = sg['GroupName'] if groupName in groups: # Prioritise groups from the current VPC # even if current VPC is EC2-Classic if groups[groupName].get('VpcId') == vpc_id: # Group saved already matches current VPC, change nothing pass elif vpc_id is None and groups[groupName].get('VpcId') is None: # We're in EC2 classic, and the group already saved is as well # No VPC groups can be used alongside EC2 classic groups pass else: # the current SG stored has no direct match, so we can replace it groups[groupName] = sg else: groups[groupName] = sg if group_id and sg['GroupId'] == group_id: group = sg elif groupName == name and (vpc_id is None or sg.get('VpcId') == vpc_id): group = sg # Ensure requested group is absent if state == 'absent': if group: # found a match, delete it try: if not module.check_mode: client.delete_security_group(GroupId=group['GroupId']) except botocore.exceptions.ClientError as e: module.fail_json(msg="Unable to delete security group '%s' - %s" % (group, e), exception=traceback.format_exc(), **camel_dict_to_snake_dict(e.response)) else: group = None changed = True else: # no match found, no changes required pass # Ensure requested group is present elif state == 'present': if group: # existing group if group['Description'] != description: module.warn("Group description does not match existing group. Descriptions cannot be changed without deleting " "and re-creating the security group. Try using state=absent to delete, then rerunning this task.") # if the group doesn't exist, create it now else: # no match found, create it if not module.check_mode: params = dict(GroupName=name, Description=description) if vpc_id: params['VpcId'] = vpc_id group = client.create_security_group(**params) # When a group is created, an egress_rule ALLOW ALL # to 0.0.0.0/0 is added automatically but it's not # reflected in the object returned by the AWS API # call. We re-read the group for getting an updated object # amazon sometimes takes a couple seconds to update the security group so wait till it exists while True: group = get_security_groups_with_backoff(client, GroupIds=[group['GroupId']])['SecurityGroups'][0] if group.get('VpcId') and not group.get('IpPermissionsEgress'): pass else: break changed = True if tags is not None: current_tags = boto3_tag_list_to_ansible_dict(group.get('Tags', [])) tags_need_modify, tags_to_delete = compare_aws_tags(current_tags, tags, purge_tags) if tags_to_delete: try: client.delete_tags(Resources=[group['GroupId']], Tags=[{'Key': tag} for tag in tags_to_delete]) except botocore.exceptions.ClientError as e: module.fail_json(msg=e.message, exception=traceback.format_exc(), **camel_dict_to_snake_dict(e.response)) changed = True # Add/update tags if tags_need_modify: try: client.create_tags(Resources=[group['GroupId']], Tags=ansible_dict_to_boto3_tag_list(tags_need_modify)) except botocore.exceptions.ClientError as e: module.fail_json(msg=e.message, exception=traceback.format_exc(), **camel_dict_to_snake_dict(e.response)) changed = True else: module.fail_json(msg="Unsupported state requested: %s" % state) # create a lookup for all existing rules on the group ip_permission = [] if group: # Manage ingress rules groupRules = {} add_rules_to_lookup(group['IpPermissions'], group['GroupId'], 'in', groupRules) # Now, go through all provided rules and ensure they are there. if rules is not None: for rule in rules: validate_rule(module, rule) group_id, ip, ipv6, target_group_created = get_target_from_rule(module, client, rule, name, group, groups, vpc_id) if target_group_created: changed = True if rule['proto'] in ('all', '-1', -1): rule['proto'] = -1 rule['from_port'] = None rule['to_port'] = None if group_id: rule_id = make_rule_key('in', rule, group['GroupId'], group_id) if rule_id in groupRules: del groupRules[rule_id] else: if not module.check_mode: ip_permission = serialize_group_grant(group_id, rule) if ip_permission: ips = ip_permission if vpc_id: [useridpair.update({'VpcId': vpc_id}) for useridpair in ip_permission.get('UserIdGroupPairs', [])] try: client.authorize_security_group_ingress(GroupId=group['GroupId'], IpPermissions=[ips]) except botocore.exceptions.ClientError as e: module.fail_json( msg="Unable to authorize ingress for group %s security group '%s' - %s" % (group_id, group['GroupName'], e), exception=traceback.format_exc(), **camel_dict_to_snake_dict(e.response)) changed = True elif ip: # Convert ip to list we can iterate over if ip and not isinstance(ip, list): ip = [ip] changed, ip_permission = authorize_ip("in", changed, client, group, groupRules, ip, ip_permission, module, rule, "ipv4") elif ipv6: # Convert ip to list we can iterate over if not isinstance(ipv6, list): ipv6 = [ipv6] # If rule already exists, don't later delete it changed, ip_permission = authorize_ip("in", changed, client, group, groupRules, ipv6, ip_permission, module, rule, "ipv6") # Finally, remove anything left in the groupRules -- these will be defunct rules if purge_rules: for (rule, grant) in groupRules.values(): ip_permission = serialize_revoke(grant, rule) if not module.check_mode: try: client.revoke_security_group_ingress(GroupId=group['GroupId'], IpPermissions=[ip_permission]) except botocore.exceptions.ClientError as e: module.fail_json( msg="Unable to revoke ingress for security group '%s' - %s" % (group['GroupName'], e), exception=traceback.format_exc(), **camel_dict_to_snake_dict(e.response)) changed = True # Manage egress rules groupRules = {} add_rules_to_lookup(group['IpPermissionsEgress'], group['GroupId'], 'out', groupRules) # Now, go through all provided rules and ensure they are there. if rules_egress is not None: for rule in rules_egress: validate_rule(module, rule) group_id, ip, ipv6, target_group_created = get_target_from_rule(module, client, rule, name, group, groups, vpc_id) if target_group_created: changed = True if rule['proto'] in ('all', '-1', -1): rule['proto'] = -1 rule['from_port'] = None rule['to_port'] = None if group_id: rule_id = make_rule_key('out', rule, group['GroupId'], group_id) if rule_id in groupRules: del groupRules[rule_id] else: if not module.check_mode: ip_permission = serialize_group_grant(group_id, rule) if ip_permission: ips = ip_permission if vpc_id: [useridpair.update({'VpcId': vpc_id}) for useridpair in ip_permission.get('UserIdGroupPairs', [])] try: client.authorize_security_group_egress(GroupId=group['GroupId'], IpPermissions=[ips]) except botocore.exceptions.ClientError as e: module.fail_json( msg="Unable to authorize egress for group %s security group '%s' - %s" % (group_id, group['GroupName'], e), exception=traceback.format_exc(), **camel_dict_to_snake_dict(e.response)) changed = True elif ip: # Convert ip to list we can iterate over if not isinstance(ip, list): ip = [ip] changed, ip_permission = authorize_ip("out", changed, client, group, groupRules, ip, ip_permission, module, rule, "ipv4") elif ipv6: # Convert ip to list we can iterate over if not isinstance(ipv6, list): ipv6 = [ipv6] # If rule already exists, don't later delete it changed, ip_permission = authorize_ip("out", changed, client, group, groupRules, ipv6, ip_permission, module, rule, "ipv6") elif vpc_id is not None: # when no egress rules are specified and we're in a VPC, # we add in a default allow all out rule, which was the # default behavior before egress rules were added default_egress_rule = 'out--1-None-None-' + group['GroupId'] + '-0.0.0.0/0' if default_egress_rule not in groupRules: if not module.check_mode: ip_permission = [{'IpProtocol': '-1', 'IpRanges': [{'CidrIp': '0.0.0.0/0'}] } ] try: client.authorize_security_group_egress(GroupId=group['GroupId'], IpPermissions=ip_permission) except botocore.exceptions.ClientError as e: module.fail_json(msg="Unable to authorize egress for ip %s security group '%s' - %s" % ('0.0.0.0/0', group['GroupName'], e), exception=traceback.format_exc(), **camel_dict_to_snake_dict(e.response)) changed = True else: # make sure the default egress rule is not removed del groupRules[default_egress_rule] # Finally, remove anything left in the groupRules -- these will be defunct rules if purge_rules_egress and vpc_id is not None: for (rule, grant) in groupRules.values(): # we shouldn't be revoking 0.0.0.0 egress if grant != '0.0.0.0/0': ip_permission = serialize_revoke(grant, rule) if not module.check_mode: try: client.revoke_security_group_egress(GroupId=group['GroupId'], IpPermissions=[ip_permission]) except botocore.exceptions.ClientError as e: module.fail_json(msg="Unable to revoke egress for ip %s security group '%s' - %s" % (grant, group['GroupName'], e), exception=traceback.format_exc(), **camel_dict_to_snake_dict(e.response)) changed = True if group: security_group = get_security_groups_with_backoff(client, GroupIds=[group['GroupId']])['SecurityGroups'][0] security_group = camel_dict_to_snake_dict(security_group) security_group['tags'] = boto3_tag_list_to_ansible_dict(security_group.get('tags', []), tag_name_key_name='key', tag_value_key_name='value') module.exit_json(changed=changed, **security_group) else: module.exit_json(changed=changed, group_id=None)
def create_or_update_dynamo_table(connection, module, boto3_dynamodb=None, boto3_sts=None, region=None): table_name = module.params.get('name') hash_key_name = module.params.get('hash_key_name') hash_key_type = module.params.get('hash_key_type') range_key_name = module.params.get('range_key_name') range_key_type = module.params.get('range_key_type') read_capacity = module.params.get('read_capacity') write_capacity = module.params.get('write_capacity') all_indexes = module.params.get('indexes') tags = module.params.get('tags') wait_for_active_timeout = module.params.get('wait_for_active_timeout') for index in all_indexes: validate_index(index, module) schema = get_schema_param(hash_key_name, hash_key_type, range_key_name, range_key_type) throughput = { 'read': read_capacity, 'write': write_capacity } indexes, global_indexes = get_indexes(all_indexes) result = dict( region=region, table_name=table_name, hash_key_name=hash_key_name, hash_key_type=hash_key_type, range_key_name=range_key_name, range_key_type=range_key_type, read_capacity=read_capacity, write_capacity=write_capacity, indexes=all_indexes, ) try: table = Table(table_name, connection=connection) if dynamo_table_exists(table): result['changed'] = update_dynamo_table(table, throughput=throughput, check_mode=module.check_mode, global_indexes=global_indexes) else: if not module.check_mode: Table.create(table_name, connection=connection, schema=schema, throughput=throughput, indexes=indexes, global_indexes=global_indexes) result['changed'] = True if not module.check_mode: result['table_status'] = table.describe()['Table']['TableStatus'] if tags: # only tables which are active can be tagged wait_until_table_active(module, table, wait_for_active_timeout) account_id = get_account_id(boto3_sts) boto3_dynamodb.tag_resource( ResourceArn='arn:aws:dynamodb:' + region + ':' + account_id + ':table/' + table_name, Tags=ansible_dict_to_boto3_tag_list(tags)) result['tags'] = tags except BotoServerError: result['msg'] = 'Failed to create/update dynamo table due to error: ' + traceback.format_exc() module.fail_json(**result) else: module.exit_json(**result)
def main(): argument_spec = ec2_argument_spec() argument_spec.update( dict(name=dict(), group_id=dict(), description=dict(), vpc_id=dict(), rules=dict(type='list'), rules_egress=dict(type='list'), state=dict(default='present', type='str', choices=['present', 'absent']), purge_rules=dict(default=True, required=False, type='bool'), purge_rules_egress=dict(default=True, required=False, type='bool'), tags=dict(required=False, type='dict', aliases=['resource_tags']), purge_tags=dict(default=True, required=False, type='bool'))) module = AnsibleModule( argument_spec=argument_spec, supports_check_mode=True, required_one_of=[['name', 'group_id']], required_if=[['state', 'present', ['name']]], ) if not HAS_BOTO3: module.fail_json(msg='boto3 required for this module') name = module.params['name'] group_id = module.params['group_id'] description = module.params['description'] vpc_id = module.params['vpc_id'] rules = deduplicate_rules_args( rules_expand_sources(rules_expand_ports(module.params['rules']))) rules_egress = deduplicate_rules_args( rules_expand_sources(rules_expand_ports( module.params['rules_egress']))) state = module.params.get('state') purge_rules = module.params['purge_rules'] purge_rules_egress = module.params['purge_rules_egress'] tags = module.params['tags'] purge_tags = module.params['purge_tags'] if state == 'present' and not description: module.fail_json(msg='Must provide description when state is present.') changed = False region, ec2_url, aws_connect_params = get_aws_connection_info(module, boto3=True) if not region: module.fail_json(msg="The AWS region must be specified as an " "environment variable or in the AWS credentials " "profile.") client = boto3_conn(module, conn_type='client', resource='ec2', endpoint=ec2_url, region=region, **aws_connect_params) group = None groups = dict() security_groups = [] # do get all security groups # find if the group is present try: response = get_security_groups_with_backoff(client) security_groups = response.get('SecurityGroups', []) except botocore.exceptions.NoCredentialsError as e: module.fail_json(msg="Error in describe_security_groups: %s" % "Unable to locate credentials", exception=traceback.format_exc()) except botocore.exceptions.ClientError as e: module.fail_json(msg="Error in describe_security_groups: %s" % e, exception=traceback.format_exc(), **camel_dict_to_snake_dict(e.response)) for sg in security_groups: groups[sg['GroupId']] = sg groupName = sg['GroupName'] if groupName in groups: # Prioritise groups from the current VPC # even if current VPC is EC2-Classic if groups[groupName].get('VpcId') == vpc_id: # Group saved already matches current VPC, change nothing pass elif vpc_id is None and groups[groupName].get('VpcId') is None: # We're in EC2 classic, and the group already saved is as well # No VPC groups can be used alongside EC2 classic groups pass else: # the current SG stored has no direct match, so we can replace it groups[groupName] = sg else: groups[groupName] = sg if group_id and sg['GroupId'] == group_id: group = sg elif groupName == name and (vpc_id is None or sg.get('VpcId') == vpc_id): group = sg # Ensure requested group is absent if state == 'absent': if group: # found a match, delete it try: if not module.check_mode: client.delete_security_group(GroupId=group['GroupId']) except botocore.exceptions.ClientError as e: module.fail_json( msg="Unable to delete security group '%s' - %s" % (group, e), exception=traceback.format_exc(), **camel_dict_to_snake_dict(e.response)) else: group = None changed = True else: # no match found, no changes required pass # Ensure requested group is present elif state == 'present': if group: # existing group if group['Description'] != description: module.warn( "Group description does not match existing group. Descriptions cannot be changed without deleting " "and re-creating the security group. Try using state=absent to delete, then rerunning this task." ) # if the group doesn't exist, create it now else: # no match found, create it if not module.check_mode: params = dict(GroupName=name, Description=description) if vpc_id: params['VpcId'] = vpc_id group = client.create_security_group(**params) # When a group is created, an egress_rule ALLOW ALL # to 0.0.0.0/0 is added automatically but it's not # reflected in the object returned by the AWS API # call. We re-read the group for getting an updated object # amazon sometimes takes a couple seconds to update the security group so wait till it exists while True: group = get_security_groups_with_backoff( client, GroupIds=[group['GroupId']])['SecurityGroups'][0] if group.get( 'VpcId') and not group.get('IpPermissionsEgress'): pass else: break changed = True if tags is not None: current_tags = boto3_tag_list_to_ansible_dict(group.get( 'Tags', [])) tags_need_modify, tags_to_delete = compare_aws_tags( current_tags, tags, purge_tags) if tags_to_delete: try: client.delete_tags(Resources=[group['GroupId']], Tags=[{ 'Key': tag } for tag in tags_to_delete]) except botocore.exceptions.ClientError as e: module.fail_json(msg=e.message, exception=traceback.format_exc(), **camel_dict_to_snake_dict(e.response)) changed = True # Add/update tags if tags_need_modify: try: client.create_tags( Resources=[group['GroupId']], Tags=ansible_dict_to_boto3_tag_list(tags_need_modify)) except botocore.exceptions.ClientError as e: module.fail_json(msg=e.message, exception=traceback.format_exc(), **camel_dict_to_snake_dict(e.response)) changed = True else: module.fail_json(msg="Unsupported state requested: %s" % state) # create a lookup for all existing rules on the group ip_permission = [] if group: # Manage ingress rules groupRules = {} add_rules_to_lookup(group['IpPermissions'], group['GroupId'], 'in', groupRules) # Now, go through all provided rules and ensure they are there. if rules is not None: for rule in rules: validate_rule(module, rule) group_id, ip, ipv6, target_group_created = get_target_from_rule( module, client, rule, name, group, groups, vpc_id) if target_group_created: changed = True if rule['proto'] in ('all', '-1', -1): rule['proto'] = -1 rule['from_port'] = None rule['to_port'] = None if group_id: rule_id = make_rule_key('in', rule, group['GroupId'], group_id) if rule_id in groupRules: del groupRules[rule_id] else: if not module.check_mode: ip_permission = serialize_group_grant( group_id, rule) if ip_permission: ips = ip_permission if vpc_id: [ useridpair.update({'VpcId': vpc_id}) for useridpair in ip_permission.get( 'UserIdGroupPairs', []) ] try: client.authorize_security_group_ingress( GroupId=group['GroupId'], IpPermissions=[ips]) except botocore.exceptions.ClientError as e: module.fail_json( msg= "Unable to authorize ingress for group %s security group '%s' - %s" % (group_id, group['GroupName'], e), exception=traceback.format_exc(), **camel_dict_to_snake_dict(e.response)) changed = True elif ip: # Convert ip to list we can iterate over if ip and not isinstance(ip, list): ip = [ip] changed, ip_permission = authorize_ip( "in", changed, client, group, groupRules, ip, ip_permission, module, rule, "ipv4") elif ipv6: # Convert ip to list we can iterate over if not isinstance(ipv6, list): ipv6 = [ipv6] # If rule already exists, don't later delete it changed, ip_permission = authorize_ip( "in", changed, client, group, groupRules, ipv6, ip_permission, module, rule, "ipv6") # Finally, remove anything left in the groupRules -- these will be defunct rules if purge_rules: for (rule, grant) in groupRules.values(): ip_permission = serialize_revoke(grant, rule) if not module.check_mode: try: client.revoke_security_group_ingress( GroupId=group['GroupId'], IpPermissions=[ip_permission]) except botocore.exceptions.ClientError as e: module.fail_json( msg= "Unable to revoke ingress for security group '%s' - %s" % (group['GroupName'], e), exception=traceback.format_exc(), **camel_dict_to_snake_dict(e.response)) changed = True # Manage egress rules groupRules = {} add_rules_to_lookup(group['IpPermissionsEgress'], group['GroupId'], 'out', groupRules) # Now, go through all provided rules and ensure they are there. if rules_egress is not None: for rule in rules_egress: validate_rule(module, rule) group_id, ip, ipv6, target_group_created = get_target_from_rule( module, client, rule, name, group, groups, vpc_id) if target_group_created: changed = True if rule['proto'] in ('all', '-1', -1): rule['proto'] = -1 rule['from_port'] = None rule['to_port'] = None if group_id: rule_id = make_rule_key('out', rule, group['GroupId'], group_id) if rule_id in groupRules: del groupRules[rule_id] else: if not module.check_mode: ip_permission = serialize_group_grant( group_id, rule) if ip_permission: ips = ip_permission if vpc_id: [ useridpair.update({'VpcId': vpc_id}) for useridpair in ip_permission.get( 'UserIdGroupPairs', []) ] try: client.authorize_security_group_egress( GroupId=group['GroupId'], IpPermissions=[ips]) except botocore.exceptions.ClientError as e: module.fail_json( msg= "Unable to authorize egress for group %s security group '%s' - %s" % (group_id, group['GroupName'], e), exception=traceback.format_exc(), **camel_dict_to_snake_dict(e.response)) changed = True elif ip: # Convert ip to list we can iterate over if not isinstance(ip, list): ip = [ip] changed, ip_permission = authorize_ip( "out", changed, client, group, groupRules, ip, ip_permission, module, rule, "ipv4") elif ipv6: # Convert ip to list we can iterate over if not isinstance(ipv6, list): ipv6 = [ipv6] # If rule already exists, don't later delete it changed, ip_permission = authorize_ip( "out", changed, client, group, groupRules, ipv6, ip_permission, module, rule, "ipv6") elif vpc_id is not None: # when no egress rules are specified and we're in a VPC, # we add in a default allow all out rule, which was the # default behavior before egress rules were added default_egress_rule = 'out--1-None-None-' + group[ 'GroupId'] + '-0.0.0.0/0' if default_egress_rule not in groupRules: if not module.check_mode: ip_permission = [{ 'IpProtocol': '-1', 'IpRanges': [{ 'CidrIp': '0.0.0.0/0' }] }] try: client.authorize_security_group_egress( GroupId=group['GroupId'], IpPermissions=ip_permission) except botocore.exceptions.ClientError as e: module.fail_json( msg= "Unable to authorize egress for ip %s security group '%s' - %s" % ('0.0.0.0/0', group['GroupName'], e), exception=traceback.format_exc(), **camel_dict_to_snake_dict(e.response)) changed = True else: # make sure the default egress rule is not removed del groupRules[default_egress_rule] # Finally, remove anything left in the groupRules -- these will be defunct rules if purge_rules_egress and vpc_id is not None: for (rule, grant) in groupRules.values(): # we shouldn't be revoking 0.0.0.0 egress if grant != '0.0.0.0/0': ip_permission = serialize_revoke(grant, rule) if not module.check_mode: try: client.revoke_security_group_egress( GroupId=group['GroupId'], IpPermissions=[ip_permission]) except botocore.exceptions.ClientError as e: module.fail_json( msg= "Unable to revoke egress for ip %s security group '%s' - %s" % (grant, group['GroupName'], e), exception=traceback.format_exc(), **camel_dict_to_snake_dict(e.response)) changed = True if group: security_group = get_security_groups_with_backoff( client, GroupIds=[group['GroupId']])['SecurityGroups'][0] security_group = camel_dict_to_snake_dict(security_group) security_group['tags'] = boto3_tag_list_to_ansible_dict( security_group.get('tags', []), tag_name_key_name='key', tag_value_key_name='value') module.exit_json(changed=changed, **security_group) else: module.exit_json(changed=changed, group_id=None)
def main(): argument_spec = dict( name=dict(required=True), state=dict(choices=['absent', 'present'], default='present'), tags=dict(type='dict'), ) required_if = [['state', 'present', ['tags']]] module = AnsibleAWSModule( argument_spec=argument_spec, supports_check_mode=False, required_if=required_if, ) name = module.params.get('name') state = module.params.get('state').lower() tags = module.params.get('tags') if tags: tags = ansible_dict_to_boto3_tag_list(tags, 'key', 'value') client = module.client('inspector') try: existing_target_arn = client.list_assessment_targets(filter={ 'assessmentTargetNamePattern': name }, ).get('assessmentTargetArns')[0] existing_target = camel_dict_to_snake_dict( client.describe_assessment_targets(assessmentTargetArns=[ existing_target_arn ], ).get('assessmentTargets')[0]) existing_resource_group_arn = existing_target.get('resource_group_arn') existing_resource_group_tags = client.describe_resource_groups( resourceGroupArns=[existing_resource_group_arn ], ).get('resourceGroups')[0].get('tags') target_exists = True except ( botocore.exceptions.BotoCoreError, botocore.exceptions.ClientError, ) as e: module.fail_json_aws(e, msg="trying to retrieve targets") except IndexError: target_exists = False if state == 'present' and target_exists: ansible_dict_tags = boto3_tag_list_to_ansible_dict(tags) ansible_dict_existing_tags = boto3_tag_list_to_ansible_dict( existing_resource_group_tags) tags_to_add, tags_to_remove = compare_aws_tags( ansible_dict_tags, ansible_dict_existing_tags) if not (tags_to_add or tags_to_remove): existing_target.update({'tags': ansible_dict_existing_tags}) module.exit_json(changed=False, **existing_target) else: try: updated_resource_group_arn = client.create_resource_group( resourceGroupTags=tags, ).get('resourceGroupArn') client.update_assessment_target( assessmentTargetArn=existing_target_arn, assessmentTargetName=name, resourceGroupArn=updated_resource_group_arn, ) updated_target = camel_dict_to_snake_dict( client.describe_assessment_targets(assessmentTargetArns=[ existing_target_arn ], ).get('assessmentTargets')[0]) updated_target.update({'tags': ansible_dict_tags}) module.exit_json(changed=True, **updated_target), except ( botocore.exceptions.BotoCoreError, botocore.exceptions.ClientError, ) as e: module.fail_json_aws(e, msg="trying to update target") elif state == 'present' and not target_exists: try: new_resource_group_arn = client.create_resource_group( resourceGroupTags=tags, ).get('resourceGroupArn') new_target_arn = client.create_assessment_target( assessmentTargetName=name, resourceGroupArn=new_resource_group_arn, ).get('assessmentTargetArn') new_target = camel_dict_to_snake_dict( client.describe_assessment_targets(assessmentTargetArns=[ new_target_arn ], ).get('assessmentTargets')[0]) new_target.update({'tags': boto3_tag_list_to_ansible_dict(tags)}) module.exit_json(changed=True, **new_target) except ( botocore.exceptions.BotoCoreError, botocore.exceptions.ClientError, ) as e: module.fail_json_aws(e, msg="trying to create target") elif state == 'absent' and target_exists: try: client.delete_assessment_target( assessmentTargetArn=existing_target_arn, ) module.exit_json(changed=True) except ( botocore.exceptions.BotoCoreError, botocore.exceptions.ClientError, ) as e: module.fail_json_aws(e, msg="trying to delete target") elif state == 'absent' and not target_exists: module.exit_json(changed=False)
def main(): argument_spec = dict( resource=dict(required=True), tags=dict(type='dict'), purge_tags=dict(type='bool', default=False), state=dict(default='present', choices=['present', 'absent', 'list']), ) required_if = [('state', 'present', ['tags']), ('state', 'absent', ['tags'])] module = AnsibleAWSModule(argument_spec=argument_spec, required_if=required_if, supports_check_mode=True) resource = module.params['resource'] tags = module.params['tags'] state = module.params['state'] purge_tags = module.params['purge_tags'] result = {'changed': False} ec2 = module.client('ec2') current_tags = get_tags(ec2, module, resource) if state == 'list': module.exit_json(changed=False, tags=current_tags) add_tags, remove = compare_aws_tags(current_tags, tags, purge_tags=purge_tags) remove_tags = {} if state == 'absent': for key in tags: if key in current_tags and current_tags[key] == tags[key]: remove_tags[key] = tags[key] for key in remove: remove_tags[key] = current_tags[key] if remove_tags: result['changed'] = True result['removed_tags'] = remove_tags if not module.check_mode: try: ec2.delete_tags( Resources=[resource], Tags=ansible_dict_to_boto3_tag_list(remove_tags)) except (BotoCoreError, ClientError) as e: module.fail_json_aws( e, msg='Failed to remove tags {0} from resource {1}'.format( remove_tags, resource)) if state == 'present' and add_tags: result['changed'] = True result['added_tags'] = add_tags current_tags.update(add_tags) if not module.check_mode: try: ec2.create_tags(Resources=[resource], Tags=ansible_dict_to_boto3_tag_list(add_tags)) except (BotoCoreError, ClientError) as e: module.fail_json_aws( e, msg='Failed to set tags {0} on resource {1}'.format( add_tags, resource)) result['tags'] = get_tags(ec2, module, resource) module.exit_json(**result)
def create_or_update_target_group(connection, module): changed = False new_target_group = False params = dict() params['Name'] = module.params.get("name") if module.params.get("target_type") != "lambda": params['Protocol'] = module.params.get("protocol").upper() params['Port'] = module.params.get("port") params['VpcId'] = module.params.get("vpc_id") tags = module.params.get("tags") purge_tags = module.params.get("purge_tags") deregistration_delay_timeout = module.params.get( "deregistration_delay_timeout") stickiness_enabled = module.params.get("stickiness_enabled") stickiness_lb_cookie_duration = module.params.get( "stickiness_lb_cookie_duration") stickiness_type = module.params.get("stickiness_type") health_option_keys = [ "health_check_path", "health_check_protocol", "health_check_interval", "health_check_timeout", "healthy_threshold_count", "unhealthy_threshold_count", "successful_response_codes" ] health_options = any([ module.params[health_option_key] is not None for health_option_key in health_option_keys ]) # Set health check if anything set if health_options: if module.params.get("health_check_protocol") is not None: params['HealthCheckProtocol'] = module.params.get( "health_check_protocol").upper() if module.params.get("health_check_port") is not None: params['HealthCheckPort'] = module.params.get("health_check_port") if module.params.get("health_check_interval") is not None: params['HealthCheckIntervalSeconds'] = module.params.get( "health_check_interval") if module.params.get("health_check_timeout") is not None: params['HealthCheckTimeoutSeconds'] = module.params.get( "health_check_timeout") if module.params.get("healthy_threshold_count") is not None: params['HealthyThresholdCount'] = module.params.get( "healthy_threshold_count") if module.params.get("unhealthy_threshold_count") is not None: params['UnhealthyThresholdCount'] = module.params.get( "unhealthy_threshold_count") # Only need to check response code and path for http(s) health checks protocol = module.params.get("health_check_protocol") if protocol is not None and protocol.upper() in ['HTTP', 'HTTPS']: if module.params.get("health_check_path") is not None: params['HealthCheckPath'] = module.params.get( "health_check_path") if module.params.get("successful_response_codes") is not None: params['Matcher'] = {} params['Matcher']['HttpCode'] = module.params.get( "successful_response_codes") # Get target type if module.params.get("target_type") is not None: params['TargetType'] = module.params.get("target_type") if params['TargetType'] == 'ip': fail_if_ip_target_type_not_supported(module) # Get target group tg = get_target_group(connection, module) if tg: diffs = [ param for param in ('Port', 'Protocol', 'VpcId') if tg.get(param) != params.get(param) ] if diffs: module.fail_json( msg="Cannot modify %s parameter(s) for a target group" % ", ".join(diffs)) # Target group exists so check health check parameters match what has been passed health_check_params = dict() # Modify health check if anything set if health_options: # Health check protocol if 'HealthCheckProtocol' in params and tg[ 'HealthCheckProtocol'] != params['HealthCheckProtocol']: health_check_params['HealthCheckProtocol'] = params[ 'HealthCheckProtocol'] # Health check port if 'HealthCheckPort' in params and tg['HealthCheckPort'] != params[ 'HealthCheckPort']: health_check_params['HealthCheckPort'] = params[ 'HealthCheckPort'] # Health check interval if 'HealthCheckIntervalSeconds' in params and tg[ 'HealthCheckIntervalSeconds'] != params[ 'HealthCheckIntervalSeconds']: health_check_params['HealthCheckIntervalSeconds'] = params[ 'HealthCheckIntervalSeconds'] # Health check timeout if 'HealthCheckTimeoutSeconds' in params and tg[ 'HealthCheckTimeoutSeconds'] != params[ 'HealthCheckTimeoutSeconds']: health_check_params['HealthCheckTimeoutSeconds'] = params[ 'HealthCheckTimeoutSeconds'] # Healthy threshold if 'HealthyThresholdCount' in params and tg[ 'HealthyThresholdCount'] != params['HealthyThresholdCount']: health_check_params['HealthyThresholdCount'] = params[ 'HealthyThresholdCount'] # Unhealthy threshold if 'UnhealthyThresholdCount' in params and tg[ 'UnhealthyThresholdCount'] != params[ 'UnhealthyThresholdCount']: health_check_params['UnhealthyThresholdCount'] = params[ 'UnhealthyThresholdCount'] # Only need to check response code and path for http(s) health checks if tg['HealthCheckProtocol'] in ['HTTP', 'HTTPS']: # Health check path if 'HealthCheckPath' in params and tg[ 'HealthCheckPath'] != params['HealthCheckPath']: health_check_params['HealthCheckPath'] = params[ 'HealthCheckPath'] # Matcher (successful response codes) # TODO: required and here? if 'Matcher' in params: current_matcher_list = tg['Matcher']['HttpCode'].split(',') requested_matcher_list = params['Matcher'][ 'HttpCode'].split(',') if set(current_matcher_list) != set( requested_matcher_list): health_check_params['Matcher'] = {} health_check_params['Matcher']['HttpCode'] = ','.join( requested_matcher_list) try: if health_check_params: connection.modify_target_group( TargetGroupArn=tg['TargetGroupArn'], **health_check_params) changed = True except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: module.fail_json_aws(e, msg="Couldn't update target group") # Do we need to modify targets? if module.params.get("modify_targets"): # get list of current target instances. I can't see anything like a describe targets in the doco so # describe_target_health seems to be the only way to get them try: current_targets = connection.describe_target_health( TargetGroupArn=tg['TargetGroupArn']) except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: module.fail_json_aws(e, msg="Couldn't get target group health") if module.params.get("targets"): if module.params.get("target_type") != "lambda": params['Targets'] = module.params.get("targets") # Correct type of target ports for target in params['Targets']: target['Port'] = int( target.get('Port', module.params.get('port'))) current_instance_ids = [] for instance in current_targets[ 'TargetHealthDescriptions']: current_instance_ids.append(instance['Target']['Id']) new_instance_ids = [] for instance in params['Targets']: new_instance_ids.append(instance['Id']) add_instances = set(new_instance_ids) - set( current_instance_ids) if add_instances: instances_to_add = [] for target in params['Targets']: if target['Id'] in add_instances: instances_to_add.append({ 'Id': target['Id'], 'Port': target['Port'] }) changed = True try: connection.register_targets( TargetGroupArn=tg['TargetGroupArn'], Targets=instances_to_add) except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: module.fail_json_aws( e, msg="Couldn't register targets") if module.params.get("wait"): status_achieved, registered_instances = wait_for_status( connection, module, tg['TargetGroupArn'], instances_to_add, 'healthy') if not status_achieved: module.fail_json( msg= 'Error waiting for target registration to be healthy - please check the AWS console' ) remove_instances = set(current_instance_ids) - set( new_instance_ids) if remove_instances: instances_to_remove = [] for target in current_targets[ 'TargetHealthDescriptions']: if target['Target']['Id'] in remove_instances: instances_to_remove.append({ 'Id': target['Target']['Id'], 'Port': target['Target']['Port'] }) changed = True try: connection.deregister_targets( TargetGroupArn=tg['TargetGroupArn'], Targets=instances_to_remove) except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: module.fail_json_aws(e, msg="Couldn't remove targets") if module.params.get("wait"): status_achieved, registered_instances = wait_for_status( connection, module, tg['TargetGroupArn'], instances_to_remove, 'unused') if not status_achieved: module.fail_json( msg= 'Error waiting for target deregistration - please check the AWS console' ) # register lambda target else: try: changed = False target = module.params.get("targets")[0] if len(current_targets["TargetHealthDescriptions"] ) == 0: changed = True else: for item in current_targets[ "TargetHealthDescriptions"]: if target["Id"] != item["Target"]["Id"]: changed = True break # only one target is possible with lambda if changed: if target.get("Id"): response = connection.register_targets( TargetGroupArn=tg['TargetGroupArn'], Targets=[{ "Id": target['Id'] }]) except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: module.fail_json_aws(e, msg="Couldn't register targets") else: if module.params.get("target_type") != "lambda": current_instances = current_targets[ 'TargetHealthDescriptions'] if current_instances: instances_to_remove = [] for target in current_targets[ 'TargetHealthDescriptions']: instances_to_remove.append({ 'Id': target['Target']['Id'], 'Port': target['Target']['Port'] }) changed = True try: connection.deregister_targets( TargetGroupArn=tg['TargetGroupArn'], Targets=instances_to_remove) except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: module.fail_json_aws(e, msg="Couldn't remove targets") if module.params.get("wait"): status_achieved, registered_instances = wait_for_status( connection, module, tg['TargetGroupArn'], instances_to_remove, 'unused') if not status_achieved: module.fail_json( msg= 'Error waiting for target deregistration - please check the AWS console' ) # remove lambda targets else: changed = False if current_targets["TargetHealthDescriptions"]: changed = True # only one target is possible with lambda target_to_remove = current_targets[ "TargetHealthDescriptions"][0]["Target"]["Id"] if changed: connection.deregister_targets( TargetGroupArn=tg['TargetGroupArn'], Targets=[{ "Id": target_to_remove }]) else: try: connection.create_target_group(**params) changed = True new_target_group = True except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: module.fail_json_aws(e, msg="Couldn't create target group") tg = get_target_group(connection, module) if module.params.get("targets"): if module.params.get("target_type") != "lambda": params['Targets'] = module.params.get("targets") try: connection.register_targets( TargetGroupArn=tg['TargetGroupArn'], Targets=params['Targets']) except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: module.fail_json_aws(e, msg="Couldn't register targets") if module.params.get("wait"): status_achieved, registered_instances = wait_for_status( connection, module, tg['TargetGroupArn'], params['Targets'], 'healthy') if not status_achieved: module.fail_json( msg= 'Error waiting for target registration to be healthy - please check the AWS console' ) else: try: target = module.params.get("targets")[0] response = connection.register_targets( TargetGroupArn=tg['TargetGroupArn'], Targets=[{ "Id": target["Id"] }]) changed = True except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: module.fail_json_aws(e, msg="Couldn't register targets") # Now set target group attributes update_attributes = [] # Get current attributes current_tg_attributes = get_tg_attributes(connection, module, tg['TargetGroupArn']) if deregistration_delay_timeout is not None: if str(deregistration_delay_timeout) != current_tg_attributes[ 'deregistration_delay_timeout_seconds']: update_attributes.append({ 'Key': 'deregistration_delay.timeout_seconds', 'Value': str(deregistration_delay_timeout) }) if stickiness_enabled is not None: if stickiness_enabled and current_tg_attributes[ 'stickiness_enabled'] != "true": update_attributes.append({ 'Key': 'stickiness.enabled', 'Value': 'true' }) if stickiness_lb_cookie_duration is not None: if str(stickiness_lb_cookie_duration) != current_tg_attributes[ 'stickiness_lb_cookie_duration_seconds']: update_attributes.append({ 'Key': 'stickiness.lb_cookie.duration_seconds', 'Value': str(stickiness_lb_cookie_duration) }) if stickiness_type is not None and "stickiness_type" in current_tg_attributes: if stickiness_type != current_tg_attributes['stickiness_type']: update_attributes.append({ 'Key': 'stickiness.type', 'Value': stickiness_type }) if update_attributes: try: connection.modify_target_group_attributes( TargetGroupArn=tg['TargetGroupArn'], Attributes=update_attributes) changed = True except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: # Something went wrong setting attributes. If this target group was created during this task, delete it to leave a consistent state if new_target_group: connection.delete_target_group( TargetGroupArn=tg['TargetGroupArn']) module.fail_json_aws(e, msg="Couldn't delete target group") # Tags - only need to play with tags if tags parameter has been set to something if tags: # Get tags current_tags = get_target_group_tags(connection, module, tg['TargetGroupArn']) # Delete necessary tags tags_need_modify, tags_to_delete = compare_aws_tags( boto3_tag_list_to_ansible_dict(current_tags), tags, purge_tags) if tags_to_delete: try: connection.remove_tags(ResourceArns=[tg['TargetGroupArn']], TagKeys=tags_to_delete) except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: module.fail_json_aws( e, msg="Couldn't delete tags from target group") changed = True # Add/update tags if tags_need_modify: try: connection.add_tags( ResourceArns=[tg['TargetGroupArn']], Tags=ansible_dict_to_boto3_tag_list(tags_need_modify)) except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: module.fail_json_aws(e, msg="Couldn't add tags to target group") changed = True # Get the target group again tg = get_target_group(connection, module) # Get the target group attributes again tg.update(get_tg_attributes(connection, module, tg['TargetGroupArn'])) # Convert tg to snake_case snaked_tg = camel_dict_to_snake_dict(tg) snaked_tg['tags'] = boto3_tag_list_to_ansible_dict( get_target_group_tags(connection, module, tg['TargetGroupArn'])) module.exit_json(changed=changed, **snaked_tg)
def main(): module = AnsibleAWSModule( argument_spec={ 'name': dict(required=True), 'state': dict(choices=['present', 'absent'], default='present'), 'description': dict(default=""), 'kms_key_id': dict(), 'secret_type': dict(choices=['binary', 'string'], default="string"), 'secret': dict(default=""), 'tags': dict(type='dict', default={}), 'rotation_lambda': dict(), 'rotation_interval': dict(type='int', default=30), 'recovery_window': dict(type='int', default=30), }, supports_check_mode=True, ) changed = False state = module.params.get('state') secrets_mgr = SecretsManagerInterface(module) recovery_window = module.params.get('recovery_window') secret = Secret(module.params.get('name'), module.params.get('secret_type'), module.params.get('secret'), description=module.params.get('description'), kms_key_id=module.params.get('kms_key_id'), tags=module.params.get('tags'), lambda_arn=module.params.get('rotation_lambda'), rotation_interval=module.params.get('rotation_interval')) current_secret = secrets_mgr.get_secret(secret.name) if state == 'absent': if current_secret: if not current_secret.get("DeletedDate"): result = camel_dict_to_snake_dict( secrets_mgr.delete_secret(secret.name, recovery_window=recovery_window)) changed = True elif current_secret.get("DeletedDate") and recovery_window == 0: result = camel_dict_to_snake_dict( secrets_mgr.delete_secret(secret.name, recovery_window=recovery_window)) changed = True else: result = "secret does not exist" if state == 'present': if current_secret is None: result = secrets_mgr.create_secret(secret) changed = True else: if current_secret.get("DeletedDate"): secrets_mgr.restore_secret(secret.name) changed = True if not secrets_mgr.secrets_match(secret, current_secret): result = secrets_mgr.update_secret(secret) changed = True if not rotation_match(secret, current_secret): result = secrets_mgr.update_rotation(secret) changed = True current_tags = boto3_tag_list_to_ansible_dict( current_secret.get('Tags', [])) tags_to_add, tags_to_remove = compare_aws_tags( current_tags, secret.tags) if tags_to_add: secrets_mgr.tag_secret( secret.name, ansible_dict_to_boto3_tag_list(tags_to_add)) changed = True if tags_to_remove: secrets_mgr.untag_secret(secret.name, tags_to_remove) changed = True result = camel_dict_to_snake_dict(secrets_mgr.get_secret(secret.name)) result.pop("response_metadata") module.exit_json(changed=changed, secret=result)
def create_or_update_elb(connection, connection_ec2, module): """Create ELB or modify main attributes. json_exit here""" changed = False new_load_balancer = False params = dict() params['Name'] = module.params.get("name") params['Subnets'] = module.params.get("subnets") try: params['SecurityGroups'] = get_ec2_security_group_ids_from_names(module.params.get('security_groups'), connection_ec2, boto3=True) except ValueError as e: module.fail_json(msg=str(e), exception=traceback.format_exc()) except ClientError as e: module.fail_json(msg=e.message, exception=traceback.format_exc(), **camel_dict_to_snake_dict(e.response)) except NoCredentialsError as e: module.fail_json(msg="AWS authentication problem. " + e.message, exception=traceback.format_exc()) params['Scheme'] = module.params.get("scheme") if module.params.get("tags"): params['Tags'] = ansible_dict_to_boto3_tag_list(module.params.get("tags")) purge_tags = module.params.get("purge_tags") access_logs_enabled = module.params.get("access_logs_enabled") access_logs_s3_bucket = module.params.get("access_logs_s3_bucket") access_logs_s3_prefix = module.params.get("access_logs_s3_prefix") deletion_protection = module.params.get("deletion_protection") idle_timeout = module.params.get("idle_timeout") # Does the ELB currently exist? elb = get_elb(connection, module) if elb: # ELB exists so check subnets, security groups and tags match what has been passed # Subnets if set(_get_subnet_ids_from_subnet_list(elb['AvailabilityZones'])) != set(params['Subnets']): try: connection.set_subnets(LoadBalancerArn=elb['LoadBalancerArn'], Subnets=params['Subnets']) except ClientError as e: module.fail_json(msg=e.message, exception=traceback.format_exc(), **camel_dict_to_snake_dict(e.response)) changed = True # Security Groups if set(elb['SecurityGroups']) != set(params['SecurityGroups']): try: connection.set_security_groups(LoadBalancerArn=elb['LoadBalancerArn'], SecurityGroups=params['SecurityGroups']) except ClientError as e: module.fail_json(msg=e.message, exception=traceback.format_exc(), **camel_dict_to_snake_dict(e.response)) changed = True # Tags - only need to play with tags if tags parameter has been set to something if module.params.get("tags"): try: elb_tags = connection.describe_tags(ResourceArns=[elb['LoadBalancerArn']])['TagDescriptions'][0]['Tags'] except ClientError as e: module.fail_json(msg=e.message, exception=traceback.format_exc(), **camel_dict_to_snake_dict(e.response)) # Delete necessary tags tags_need_modify, tags_to_delete = compare_aws_tags(boto3_tag_list_to_ansible_dict(elb_tags), boto3_tag_list_to_ansible_dict(params['Tags']), purge_tags) if tags_to_delete: try: connection.remove_tags(ResourceArns=[elb['LoadBalancerArn']], TagKeys=tags_to_delete) except ClientError as e: module.fail_json(msg=e.message, exception=traceback.format_exc(), **camel_dict_to_snake_dict(e.response)) changed = True # Add/update tags if tags_need_modify: try: connection.add_tags(ResourceArns=[elb['LoadBalancerArn']], Tags=params['Tags']) except ClientError as e: module.fail_json(msg=e.message, exception=traceback.format_exc(), **camel_dict_to_snake_dict(e.response)) changed = True else: try: elb = connection.create_load_balancer(**params)['LoadBalancers'][0] changed = True new_load_balancer = True except ClientError as e: module.fail_json(msg=e.message, exception=traceback.format_exc(), **camel_dict_to_snake_dict(e.response)) if module.params.get("wait"): status_achieved, new_elb = wait_for_status(connection, module, elb['LoadBalancerArn'], 'active') # Now set ELB attributes. Use try statement here so we can remove the ELB if this stage fails update_attributes = [] # Get current attributes current_elb_attributes = get_elb_attributes(connection, module, elb['LoadBalancerArn']) if access_logs_enabled and current_elb_attributes['access_logs_s3_enabled'] != "true": update_attributes.append({'Key': 'access_logs.s3.enabled', 'Value': "true"}) if not access_logs_enabled and current_elb_attributes['access_logs_s3_enabled'] != "false": update_attributes.append({'Key': 'access_logs.s3.enabled', 'Value': 'false'}) if access_logs_s3_bucket is not None and access_logs_s3_bucket != current_elb_attributes['access_logs_s3_bucket']: update_attributes.append({'Key': 'access_logs.s3.bucket', 'Value': access_logs_s3_bucket}) if access_logs_s3_prefix is not None and access_logs_s3_prefix != current_elb_attributes['access_logs_s3_prefix']: update_attributes.append({'Key': 'access_logs.s3.prefix', 'Value': access_logs_s3_prefix}) if deletion_protection and current_elb_attributes['deletion_protection_enabled'] != "true": update_attributes.append({'Key': 'deletion_protection.enabled', 'Value': "true"}) if not deletion_protection and current_elb_attributes['deletion_protection_enabled'] != "false": update_attributes.append({'Key': 'deletion_protection.enabled', 'Value': "false"}) if idle_timeout is not None and str(idle_timeout) != current_elb_attributes['idle_timeout_timeout_seconds']: update_attributes.append({'Key': 'idle_timeout.timeout_seconds', 'Value': str(idle_timeout)}) if update_attributes: try: connection.modify_load_balancer_attributes(LoadBalancerArn=elb['LoadBalancerArn'], Attributes=update_attributes) changed = True except ClientError as e: # Something went wrong setting attributes. If this ELB was created during this task, delete it to leave a consistent state if new_load_balancer: connection.delete_load_balancer(LoadBalancerArn=elb['LoadBalancerArn']) module.fail_json(msg=e.message, exception=traceback.format_exc(), **camel_dict_to_snake_dict(e.response)) # Now, if required, set ELB listeners. Use try statement here so we can remove the ELB if this stage fails try: listener_changed = create_or_update_elb_listeners(connection, module, elb) if listener_changed: changed = True except ClientError as e: # Something went wrong setting listeners. If this ELB was created during this task, delete it to leave a consistent state if new_load_balancer: connection.delete_load_balancer(LoadBalancerArn=elb['LoadBalancerArn']) module.fail_json(msg=e.message, exception=traceback.format_exc(), **camel_dict_to_snake_dict(e.response)) # Get the ELB again elb = get_elb(connection, module) # Get the ELB listeners again elb['listeners'] = get_elb_listeners(connection, module, elb['LoadBalancerArn']) # For each listener, get listener rules for listener in elb['listeners']: listener['rules'] = get_listener_rules(connection, module, listener['ListenerArn']) # Get the ELB attributes again elb.update(get_elb_attributes(connection, module, elb['LoadBalancerArn'])) # Convert to snake_case snaked_elb = camel_dict_to_snake_dict(elb) # Get the tags of the ELB elb_tags = connection.describe_tags(ResourceArns=[elb['LoadBalancerArn']])['TagDescriptions'][0]['Tags'] snaked_elb['tags'] = boto3_tag_list_to_ansible_dict(elb_tags) module.exit_json(changed=changed, **snaked_elb)
def main(): argument_spec = dict( name=dict(required=True), state=dict(choices=['absent', 'present'], default='present'), tags=dict(type='dict'), ) required_if = [['state', 'present', ['tags']]] module = AnsibleAWSModule( argument_spec=argument_spec, supports_check_mode=False, required_if=required_if, ) if not HAS_BOTO3: module.fail_json(msg='boto3 and botocore are required for this module') name = module.params.get('name') state = module.params.get('state').lower() tags = module.params.get('tags') if tags: tags = ansible_dict_to_boto3_tag_list(tags, 'key', 'value') client = module.client('inspector') try: existing_target_arn = client.list_assessment_targets( filter={'assessmentTargetNamePattern': name}, ).get('assessmentTargetArns')[0] existing_target = camel_dict_to_snake_dict( client.describe_assessment_targets( assessmentTargetArns=[existing_target_arn], ).get('assessmentTargets')[0] ) existing_resource_group_arn = existing_target.get('resource_group_arn') existing_resource_group_tags = client.describe_resource_groups( resourceGroupArns=[existing_resource_group_arn], ).get('resourceGroups')[0].get('tags') target_exists = True except ( botocore.exceptions.BotoCoreError, botocore.exceptions.ClientError, ) as e: module.fail_json_aws(e, msg="trying to retrieve targets") except IndexError: target_exists = False if state == 'present' and target_exists: ansible_dict_tags = boto3_tag_list_to_ansible_dict(tags) ansible_dict_existing_tags = boto3_tag_list_to_ansible_dict( existing_resource_group_tags ) tags_to_add, tags_to_remove = compare_aws_tags( ansible_dict_tags, ansible_dict_existing_tags ) if not (tags_to_add or tags_to_remove): existing_target.update({'tags': ansible_dict_existing_tags}) module.exit_json(changed=False, **existing_target) else: try: updated_resource_group_arn = client.create_resource_group( resourceGroupTags=tags, ).get('resourceGroupArn') client.update_assessment_target( assessmentTargetArn=existing_target_arn, assessmentTargetName=name, resourceGroupArn=updated_resource_group_arn, ) updated_target = camel_dict_to_snake_dict( client.describe_assessment_targets( assessmentTargetArns=[existing_target_arn], ).get('assessmentTargets')[0] ) updated_target.update({'tags': ansible_dict_tags}) module.exit_json(changed=True, **updated_target), except ( botocore.exceptions.BotoCoreError, botocore.exceptions.ClientError, ) as e: module.fail_json_aws(e, msg="trying to update target") elif state == 'present' and not target_exists: try: new_resource_group_arn = client.create_resource_group( resourceGroupTags=tags, ).get('resourceGroupArn') new_target_arn = client.create_assessment_target( assessmentTargetName=name, resourceGroupArn=new_resource_group_arn, ).get('assessmentTargetArn') new_target = camel_dict_to_snake_dict( client.describe_assessment_targets( assessmentTargetArns=[new_target_arn], ).get('assessmentTargets')[0] ) new_target.update({'tags': boto3_tag_list_to_ansible_dict(tags)}) module.exit_json(changed=True, **new_target) except ( botocore.exceptions.BotoCoreError, botocore.exceptions.ClientError, ) as e: module.fail_json_aws(e, msg="trying to create target") elif state == 'absent' and target_exists: try: client.delete_assessment_target( assessmentTargetArn=existing_target_arn, ) module.exit_json(changed=True) except ( botocore.exceptions.BotoCoreError, botocore.exceptions.ClientError, ) as e: module.fail_json_aws(e, msg="trying to delete target") elif state == 'absent' and not target_exists: module.exit_json(changed=False)
def tag_certificate_with_backoff(self, client, arn, tags): aws_tags = ansible_dict_to_boto3_tag_list(tags) client.add_tags_to_certificate(CertificateArn=arn, Tags=aws_tags)
def update_image(module, connection, image_id): launch_permissions = module.params.get('launch_permissions') image = get_image_by_id(module, connection, image_id) if image is None: module.fail_json(msg="Image %s does not exist" % image_id, changed=False) changed = False if launch_permissions is not None: current_permissions = image['LaunchPermissions'] current_users = set(permission['UserId'] for permission in current_permissions if 'UserId' in permission) desired_users = set(str(user_id) for user_id in launch_permissions.get('user_ids', [])) current_groups = set(permission['Group'] for permission in current_permissions if 'Group' in permission) desired_groups = set(launch_permissions.get('group_names', [])) to_add_users = desired_users - current_users to_remove_users = current_users - desired_users to_add_groups = desired_groups - current_groups to_remove_groups = current_groups - desired_groups to_add = [dict(Group=group) for group in to_add_groups] + [dict(UserId=user_id) for user_id in to_add_users] to_remove = [dict(Group=group) for group in to_remove_groups] + [dict(UserId=user_id) for user_id in to_remove_users] if to_add or to_remove: try: connection.modify_image_attribute(ImageId=image_id, Attribute='launchPermission', LaunchPermission=dict(Add=to_add, Remove=to_remove)) changed = True except (botocore.exceptions.BotoCoreError, botocore.exceptions.ClientError) as e: module.fail_json_aws(e, msg="Error updating launch permissions of image %s" % image_id) desired_tags = module.params.get('tags') if desired_tags is not None: current_tags = boto3_tag_list_to_ansible_dict(image.get('Tags')) tags_to_add, tags_to_remove = compare_aws_tags(current_tags, desired_tags, purge_tags=module.params.get('purge_tags')) if tags_to_remove: try: connection.delete_tags(Resources=[image_id], Tags=[dict(Key=tagkey) for tagkey in tags_to_remove]) changed = True except (botocore.exceptions.BotoCoreError, botocore.exceptions.ClientError) as e: module.fail_json_aws(e, msg="Error updating tags") if tags_to_add: try: connection.create_tags(Resources=[image_id], Tags=ansible_dict_to_boto3_tag_list(tags_to_add)) changed = True except (botocore.exceptions.BotoCoreError, botocore.exceptions.ClientError) as e: module.fail_json_aws(e, msg="Error updating tags") description = module.params.get('description') if description and description != image['Description']: try: connection.modify_image_attribute(Attribute='Description ', ImageId=image_id, Description=dict(Value=description)) changed = True except (botocore.exceptions.BotoCoreError, botocore.exceptions.ClientError) as e: module.fail_json_aws(e, msg="Error setting description for image %s" % image_id) if changed: module.exit_json(msg="AMI updated.", changed=True, **get_ami_info(get_image_by_id(module, connection, image_id))) else: module.exit_json(msg="AMI not updated.", changed=False, **get_ami_info(get_image_by_id(module, connection, image_id)))
def create_or_update_target_group(connection, module): changed = False params = dict() params['Name'] = module.params.get("name") params['Protocol'] = module.params.get("protocol").upper() params['Port'] = module.params.get("port") params['VpcId'] = module.params.get("vpc_id") tags = module.params.get("tags") purge_tags = module.params.get("purge_tags") deregistration_delay_timeout = module.params.get("deregistration_delay_timeout") stickiness_enabled = module.params.get("stickiness_enabled") stickiness_lb_cookie_duration = module.params.get("stickiness_lb_cookie_duration") stickiness_type = module.params.get("stickiness_type") # If health check path not None, set health check attributes if module.params.get("health_check_path") is not None: params['HealthCheckPath'] = module.params.get("health_check_path") if module.params.get("health_check_protocol") is not None: params['HealthCheckProtocol'] = module.params.get("health_check_protocol").upper() if module.params.get("health_check_port") is not None: params['HealthCheckPort'] = str(module.params.get("health_check_port")) if module.params.get("health_check_interval") is not None: params['HealthCheckIntervalSeconds'] = module.params.get("health_check_interval") if module.params.get("health_check_timeout") is not None: params['HealthCheckTimeoutSeconds'] = module.params.get("health_check_timeout") if module.params.get("healthy_threshold_count") is not None: params['HealthyThresholdCount'] = module.params.get("healthy_threshold_count") if module.params.get("unhealthy_threshold_count") is not None: params['UnhealthyThresholdCount'] = module.params.get("unhealthy_threshold_count") if module.params.get("successful_response_codes") is not None: params['Matcher'] = {} params['Matcher']['HttpCode'] = module.params.get("successful_response_codes") # Get target group tg = get_target_group(connection, module) if tg: # Target group exists so check health check parameters match what has been passed health_check_params = dict() # If we have no health check path then we have nothing to modify if module.params.get("health_check_path") is not None: # Health check protocol if 'HealthCheckProtocol' in params and tg['HealthCheckProtocol'] != params['HealthCheckProtocol']: health_check_params['HealthCheckProtocol'] = params['HealthCheckProtocol'] # Health check port if 'HealthCheckPort' in params and tg['HealthCheckPort'] != params['HealthCheckPort']: health_check_params['HealthCheckPort'] = params['HealthCheckPort'] # Health check path if 'HealthCheckPath'in params and tg['HealthCheckPath'] != params['HealthCheckPath']: health_check_params['HealthCheckPath'] = params['HealthCheckPath'] # Health check interval if 'HealthCheckIntervalSeconds' in params and tg['HealthCheckIntervalSeconds'] != params['HealthCheckIntervalSeconds']: health_check_params['HealthCheckIntervalSeconds'] = params['HealthCheckIntervalSeconds'] # Health check timeout if 'HealthCheckTimeoutSeconds' in params and tg['HealthCheckTimeoutSeconds'] != params['HealthCheckTimeoutSeconds']: health_check_params['HealthCheckTimeoutSeconds'] = params['HealthCheckTimeoutSeconds'] # Healthy threshold if 'HealthyThresholdCount' in params and tg['HealthyThresholdCount'] != params['HealthyThresholdCount']: health_check_params['HealthyThresholdCount'] = params['HealthyThresholdCount'] # Unhealthy threshold if 'UnhealthyThresholdCount' in params and tg['UnhealthyThresholdCount'] != params['UnhealthyThresholdCount']: health_check_params['UnhealthyThresholdCount'] = params['UnhealthyThresholdCount'] # Matcher (successful response codes) # TODO: required and here? if 'Matcher' in params: current_matcher_list = tg['Matcher']['HttpCode'].split(',') requested_matcher_list = params['Matcher']['HttpCode'].split(',') if set(current_matcher_list) != set(requested_matcher_list): health_check_params['Matcher'] = {} health_check_params['Matcher']['HttpCode'] = ','.join(requested_matcher_list) try: if health_check_params: connection.modify_target_group(TargetGroupArn=tg['TargetGroupArn'], **health_check_params) changed = True except ClientError as e: module.fail_json(msg=e.message, exception=traceback.format_exc(), **camel_dict_to_snake_dict(e.response)) # Do we need to modify targets? if module.params.get("modify_targets"): if module.params.get("targets"): params['Targets'] = module.params.get("targets") # get list of current target instances. I can't see anything like a describe targets in the doco so # describe_target_health seems to be the only way to get them try: current_targets = connection.describe_target_health(TargetGroupArn=tg['TargetGroupArn']) except ClientError as e: module.fail_json(msg=e.message, exception=traceback.format_exc(), **camel_dict_to_snake_dict(e.response)) current_instance_ids = [] for instance in current_targets['TargetHealthDescriptions']: current_instance_ids.append(instance['Target']['Id']) new_instance_ids = [] for instance in params['Targets']: new_instance_ids.append(instance['Id']) add_instances = set(new_instance_ids) - set(current_instance_ids) if add_instances: instances_to_add = [] for target in params['Targets']: if target['Id'] in add_instances: instances_to_add.append(target) changed = True try: connection.register_targets(TargetGroupArn=tg['TargetGroupArn'], Targets=instances_to_add) except ClientError as e: module.fail_json(msg=e.message, exception=traceback.format_exc(), **camel_dict_to_snake_dict(e.response)) if module.params.get("wait"): status_achieved, registered_instances = wait_for_status(connection, module, tg['TargetGroupArn'], instances_to_add, 'healthy') if not status_achieved: module.fail_json(msg='Error waiting for target registration - please check the AWS console') remove_instances = set(current_instance_ids) - set(new_instance_ids) if remove_instances: instances_to_remove = [] for target in current_targets['TargetHealthDescriptions']: if target['Target']['Id'] in remove_instances: instances_to_remove.append({'Id': target['Target']['Id'], 'Port': target['Target']['Port']}) changed = True try: connection.deregister_targets(TargetGroupArn=tg['TargetGroupArn'], Targets=instances_to_remove) except ClientError as e: module.fail_json(msg=e.message, exception=traceback.format_exc(), **camel_dict_to_snake_dict(e.response)) if module.params.get("wait"): status_achieved, registered_instances = wait_for_status(connection, module, tg['TargetGroupArn'], instances_to_remove, 'unused') if not status_achieved: module.fail_json(msg='Error waiting for target deregistration - please check the AWS console') else: try: current_targets = connection.describe_target_health(TargetGroupArn=tg['TargetGroupArn']) except ClientError as e: module.fail_json(msg=e.message, exception=traceback.format_exc(), **camel_dict_to_snake_dict(e.response)) current_instances = current_targets['TargetHealthDescriptions'] if current_instances: instances_to_remove = [] for target in current_targets['TargetHealthDescriptions']: instances_to_remove.append({'Id': target['Target']['Id'], 'Port': target['Target']['Port']}) changed = True try: connection.deregister_targets(TargetGroupArn=tg['TargetGroupArn'], Targets=instances_to_remove) except ClientError as e: module.fail_json(msg=e.message, exception=traceback.format_exc(), **camel_dict_to_snake_dict(e.response)) if module.params.get("wait"): status_achieved, registered_instances = wait_for_status(connection, module, tg['TargetGroupArn'], instances_to_remove, 'unused') if not status_achieved: module.fail_json(msg='Error waiting for target deregistration - please check the AWS console') else: try: connection.create_target_group(**params) changed = True new_target_group = True except ClientError as e: module.fail_json(msg=e.message, exception=traceback.format_exc(), **camel_dict_to_snake_dict(e.response)) tg = get_target_group(connection, module) if module.params.get("targets"): params['Targets'] = module.params.get("targets") try: connection.register_targets(TargetGroupArn=tg['TargetGroupArn'], Targets=params['Targets']) except ClientError as e: module.fail_json(msg=e.message, exception=traceback.format_exc(), **camel_dict_to_snake_dict(e.response)) if module.params.get("wait"): status_achieved, registered_instances = wait_for_status(connection, module, tg['TargetGroupArn'], params['Targets'], 'healthy') if not status_achieved: module.fail_json(msg='Error waiting for target registration - please check the AWS console') # Now set target group attributes update_attributes = [] # Get current attributes current_tg_attributes = get_tg_attributes(connection, module, tg['TargetGroupArn']) if deregistration_delay_timeout is not None: if str(deregistration_delay_timeout) != current_tg_attributes['deregistration_delay_timeout_seconds']: update_attributes.append({'Key': 'deregistration_delay.timeout_seconds', 'Value': str(deregistration_delay_timeout)}) if stickiness_enabled is not None: if stickiness_enabled and current_tg_attributes['stickiness_enabled'] != "true": update_attributes.append({'Key': 'stickiness.enabled', 'Value': 'true'}) if stickiness_lb_cookie_duration is not None: if str(stickiness_lb_cookie_duration) != current_tg_attributes['stickiness_lb_cookie_duration_seconds']: update_attributes.append({'Key': 'stickiness.lb_cookie.duration_seconds', 'Value': str(stickiness_lb_cookie_duration)}) if stickiness_type is not None: if stickiness_type != current_tg_attributes['stickiness_type']: update_attributes.append({'Key': 'stickiness.type', 'Value': stickiness_type}) if update_attributes: try: connection.modify_target_group_attributes(TargetGroupArn=tg['TargetGroupArn'], Attributes=update_attributes) changed = True except ClientError as e: # Something went wrong setting attributes. If this target group was created during this task, delete it to leave a consistent state if new_target_group: connection.delete_target_group(TargetGroupArn=tg['TargetGroupArn']) module.fail_json(msg=e.message, exception=traceback.format_exc(), **camel_dict_to_snake_dict(e.response)) # Tags - only need to play with tags if tags parameter has been set to something if tags: # Get tags current_tags = get_target_group_tags(connection, module, tg['TargetGroupArn']) # Delete necessary tags tags_need_modify, tags_to_delete = compare_aws_tags(boto3_tag_list_to_ansible_dict(current_tags), tags, purge_tags) if tags_to_delete: try: connection.remove_tags(ResourceArns=[tg['TargetGroupArn']], TagKeys=tags_to_delete) except ClientError as e: module.fail_json(msg=e.message, exception=traceback.format_exc(), **camel_dict_to_snake_dict(e.response)) changed = True # Add/update tags if tags_need_modify: try: connection.add_tags(ResourceArns=[tg['TargetGroupArn']], Tags=ansible_dict_to_boto3_tag_list(tags_need_modify)) except ClientError as e: module.fail_json(msg=e.message, exception=traceback.format_exc(), **camel_dict_to_snake_dict(e.response)) changed = True # Get the target group again tg = get_target_group(connection, module) # Get the target group attributes again tg.update(get_tg_attributes(connection, module, tg['TargetGroupArn'])) # Convert tg to snake_case snaked_tg = camel_dict_to_snake_dict(tg) snaked_tg['tags'] = boto3_tag_list_to_ansible_dict(get_target_group_tags(connection, module, tg['TargetGroupArn'])) module.exit_json(changed=changed, **snaked_tg)
def main(): argument_spec = dict( name=dict(required=True), description=dict(), wait=dict(type='bool', default=False), wait_timeout=dict(type='int', default=900), state=dict(default='present', choices=['present', 'absent']), purge_stacks=dict(type='bool', default=True), parameters=dict(type='dict', default={}), template=dict(type='path'), template_url=dict(), template_body=dict(), capabilities=dict(type='list', choices=['CAPABILITY_IAM', 'CAPABILITY_NAMED_IAM']), regions=dict(type='list'), accounts=dict(type='list'), failure_tolerance=dict( type='dict', default={}, options=dict( fail_count=dict(type='int'), fail_percentage=dict(type='int'), parallel_percentage=dict(type='int'), parallel_count=dict(type='int'), ), mutually_exclusive=[ ['fail_count', 'fail_percentage'], ['parallel_count', 'parallel_percentage'], ], ), administration_role_arn=dict(aliases=['admin_role_arn', 'administration_role', 'admin_role']), execution_role_name=dict(aliases=['execution_role', 'exec_role', 'exec_role_name']), tags=dict(type='dict'), ) module = AnsibleAWSModule( argument_spec=argument_spec, mutually_exclusive=[['template_url', 'template', 'template_body']], supports_check_mode=True ) if not (module.boto3_at_least('1.6.0') and module.botocore_at_least('1.10.26')): module.fail_json(msg="Boto3 or botocore version is too low. This module requires at least boto3 1.6 and botocore 1.10.26") # Wrap the cloudformation client methods that this module uses with # automatic backoff / retry for throttling error codes cfn = module.client('cloudformation', retry_decorator=AWSRetry.jittered_backoff(retries=10, delay=3, max_delay=30)) existing_stack_set = stack_set_facts(cfn, module.params['name']) operation_uuid = to_native(uuid.uuid4()) operation_ids = [] # collect the parameters that are passed to boto3. Keeps us from having so many scalars floating around. stack_params = {} state = module.params['state'] if state == 'present' and not module.params['accounts']: module.fail_json( msg="Can't create a stack set without choosing at least one account. " "To get the ID of the current account, use the aws_caller_info module." ) module.params['accounts'] = [to_native(a) for a in module.params['accounts']] stack_params['StackSetName'] = module.params['name'] if module.params.get('description'): stack_params['Description'] = module.params['description'] if module.params.get('capabilities'): stack_params['Capabilities'] = module.params['capabilities'] if module.params['template'] is not None: with open(module.params['template'], 'r') as tpl: stack_params['TemplateBody'] = tpl.read() elif module.params['template_body'] is not None: stack_params['TemplateBody'] = module.params['template_body'] elif module.params['template_url'] is not None: stack_params['TemplateURL'] = module.params['template_url'] else: # no template is provided, but if the stack set exists already, we can use the existing one. if existing_stack_set: stack_params['UsePreviousTemplate'] = True else: module.fail_json( msg="The Stack Set {0} does not exist, and no template was provided. Provide one of `template`, " "`template_body`, or `template_url`".format(module.params['name']) ) stack_params['Parameters'] = [] for k, v in module.params['parameters'].items(): if isinstance(v, dict): # set parameter based on a dict to allow additional CFN Parameter Attributes param = dict(ParameterKey=k) if 'value' in v: param['ParameterValue'] = to_native(v['value']) if 'use_previous_value' in v and bool(v['use_previous_value']): param['UsePreviousValue'] = True param.pop('ParameterValue', None) stack_params['Parameters'].append(param) else: # allow default k/v configuration to set a template parameter stack_params['Parameters'].append({'ParameterKey': k, 'ParameterValue': str(v)}) if module.params.get('tags') and isinstance(module.params.get('tags'), dict): stack_params['Tags'] = ansible_dict_to_boto3_tag_list(module.params['tags']) if module.params.get('administration_role_arn'): # TODO loosen the semantics here to autodetect the account ID and build the ARN stack_params['AdministrationRoleARN'] = module.params['administration_role_arn'] if module.params.get('execution_role_name'): stack_params['ExecutionRoleName'] = module.params['execution_role_name'] result = {} if module.check_mode: if state == 'absent' and existing_stack_set: module.exit_json(changed=True, msg='Stack set would be deleted', meta=[]) elif state == 'absent' and not existing_stack_set: module.exit_json(changed=False, msg='Stack set doesn\'t exist', meta=[]) elif state == 'present' and not existing_stack_set: module.exit_json(changed=True, msg='New stack set would be created', meta=[]) elif state == 'present' and existing_stack_set: new_stacks, existing_stacks, unspecified_stacks = compare_stack_instances( cfn, module.params['name'], module.params['accounts'], module.params['regions'], ) if new_stacks: module.exit_json(changed=True, msg='New stack instance(s) would be created', meta=[]) elif unspecified_stacks and module.params.get('purge_stack_instances'): module.exit_json(changed=True, msg='Old stack instance(s) would be deleted', meta=[]) else: # TODO: need to check the template and other settings for correct check mode module.exit_json(changed=False, msg='No changes detected', meta=[]) changed = False if state == 'present': if not existing_stack_set: # on create this parameter has a different name, and cannot be referenced later in the job log stack_params['ClientRequestToken'] = 'Ansible-StackSet-Create-{0}'.format(operation_uuid) changed = True create_stack_set(module, stack_params, cfn) else: stack_params['OperationId'] = 'Ansible-StackSet-Update-{0}'.format(operation_uuid) operation_ids.append(stack_params['OperationId']) if module.params.get('regions'): stack_params['OperationPreferences'] = get_operation_preferences(module) changed |= update_stack_set(module, stack_params, cfn) # now create/update any appropriate stack instances new_stack_instances, existing_stack_instances, unspecified_stack_instances = compare_stack_instances( cfn, module.params['name'], module.params['accounts'], module.params['regions'], ) if new_stack_instances: operation_ids.append('Ansible-StackInstance-Create-{0}'.format(operation_uuid)) changed = True cfn.create_stack_instances( StackSetName=module.params['name'], Accounts=list(set(acct for acct, region in new_stack_instances)), Regions=list(set(region for acct, region in new_stack_instances)), OperationPreferences=get_operation_preferences(module), OperationId=operation_ids[-1], ) else: operation_ids.append('Ansible-StackInstance-Update-{0}'.format(operation_uuid)) cfn.update_stack_instances( StackSetName=module.params['name'], Accounts=list(set(acct for acct, region in existing_stack_instances)), Regions=list(set(region for acct, region in existing_stack_instances)), OperationPreferences=get_operation_preferences(module), OperationId=operation_ids[-1], ) for op in operation_ids: await_stack_set_operation( module, cfn, operation_id=op, stack_set_name=module.params['name'], max_wait=module.params.get('wait_timeout'), ) elif state == 'absent': if not existing_stack_set: module.exit_json(msg='Stack set {0} does not exist'.format(module.params['name'])) if module.params.get('purge_stack_instances') is False: pass try: cfn.delete_stack_set( StackSetName=module.params['name'], ) module.exit_json(msg='Stack set {0} deleted'.format(module.params['name'])) except is_boto3_error_code('OperationInProgressException') as e: # pylint: disable=duplicate-except module.fail_json_aws(e, msg='Cannot delete stack {0} while there is an operation in progress'.format(module.params['name'])) except is_boto3_error_code('StackSetNotEmptyException'): # pylint: disable=duplicate-except delete_instances_op = 'Ansible-StackInstance-Delete-{0}'.format(operation_uuid) cfn.delete_stack_instances( StackSetName=module.params['name'], Accounts=module.params['accounts'], Regions=module.params['regions'], RetainStacks=(not module.params.get('purge_stacks')), OperationId=delete_instances_op ) await_stack_set_operation( module, cfn, operation_id=delete_instances_op, stack_set_name=stack_params['StackSetName'], max_wait=module.params.get('wait_timeout'), ) try: cfn.delete_stack_set( StackSetName=module.params['name'], ) except is_boto3_error_code('StackSetNotEmptyException') as exc: # pylint: disable=duplicate-except # this time, it is likely that either the delete failed or there are more stacks. instances = cfn.list_stack_instances( StackSetName=module.params['name'], ) stack_states = ', '.join('(account={Account}, region={Region}, state={Status})'.format(**i) for i in instances['Summaries']) module.fail_json_aws(exc, msg='Could not purge all stacks, or not all accounts/regions were chosen for deletion: ' + stack_states) module.exit_json(changed=True, msg='Stack set {0} deleted'.format(module.params['name'])) result.update(**describe_stack_tree(module, stack_params['StackSetName'], operation_ids=operation_ids)) if any(o['status'] == 'FAILED' for o in result['operations']): module.fail_json(msg="One or more operations failed to execute", **result) module.exit_json(changed=changed, **result)
def put_bucket_tagging(s3_client, bucket_name, tags): s3_client.put_bucket_tagging(Bucket=bucket_name, Tagging={'TagSet': ansible_dict_to_boto3_tag_list(tags)})
def create_or_update_dynamo_table(connection, module, boto3_dynamodb=None, boto3_sts=None, region=None): table_name = module.params.get('name') hash_key_name = module.params.get('hash_key_name') hash_key_type = module.params.get('hash_key_type') range_key_name = module.params.get('range_key_name') range_key_type = module.params.get('range_key_type') read_capacity = module.params.get('read_capacity') write_capacity = module.params.get('write_capacity') all_indexes = module.params.get('indexes') tags = module.params.get('tags') wait_for_active_timeout = module.params.get('wait_for_active_timeout') for index in all_indexes: validate_index(index, module) schema = get_schema_param(hash_key_name, hash_key_type, range_key_name, range_key_type) throughput = {'read': read_capacity, 'write': write_capacity} indexes, global_indexes = get_indexes(all_indexes) result = dict( region=region, table_name=table_name, hash_key_name=hash_key_name, hash_key_type=hash_key_type, range_key_name=range_key_name, range_key_type=range_key_type, read_capacity=read_capacity, write_capacity=write_capacity, indexes=all_indexes, ) try: table = Table(table_name, connection=connection) if dynamo_table_exists(table): result['changed'] = update_dynamo_table( table, throughput=throughput, check_mode=module.check_mode, global_indexes=global_indexes) else: if not module.check_mode: Table.create(table_name, connection=connection, schema=schema, throughput=throughput, indexes=indexes, global_indexes=global_indexes) result['changed'] = True if not module.check_mode: result['table_status'] = table.describe()['Table']['TableStatus'] if tags: # only tables which are active can be tagged wait_until_table_active(module, table, wait_for_active_timeout) account_id = get_account_id(boto3_sts) boto3_dynamodb.tag_resource( ResourceArn='arn:aws:dynamodb:' + region + ':' + account_id + ':table/' + table_name, Tags=ansible_dict_to_boto3_tag_list(tags)) result['tags'] = tags except BotoServerError: result[ 'msg'] = 'Failed to create/update dynamo table due to error: ' + traceback.format_exc( ) module.fail_json(**result) else: module.exit_json(**result)
def create_or_update_elb(connection, connection_ec2, module): """Create ELB or modify main attributes. json_exit here""" changed = False new_load_balancer = False params = dict() params['Name'] = module.params.get("name") params['Subnets'] = module.params.get("subnets") try: params['SecurityGroups'] = get_ec2_security_group_ids_from_names( module.params.get('security_groups'), connection_ec2, boto3=True) except ValueError as e: module.fail_json(msg=str(e), exception=traceback.format_exc()) except ClientError as e: module.fail_json(msg=e.message, exception=traceback.format_exc(), **camel_dict_to_snake_dict(e.response)) except NoCredentialsError as e: module.fail_json(msg="AWS authentication problem. " + e.message, exception=traceback.format_exc()) params['Scheme'] = module.params.get("scheme") if module.params.get("tags"): params['Tags'] = ansible_dict_to_boto3_tag_list( module.params.get("tags")) purge_tags = module.params.get("purge_tags") access_logs_enabled = module.params.get("access_logs_enabled") access_logs_s3_bucket = module.params.get("access_logs_s3_bucket") access_logs_s3_prefix = module.params.get("access_logs_s3_prefix") deletion_protection = module.params.get("deletion_protection") idle_timeout = module.params.get("idle_timeout") # Does the ELB currently exist? elb = get_elb(connection, module) if elb: # ELB exists so check subnets, security groups and tags match what has been passed # Subnets if set(_get_subnet_ids_from_subnet_list( elb['AvailabilityZones'])) != set(params['Subnets']): try: connection.set_subnets(LoadBalancerArn=elb['LoadBalancerArn'], Subnets=params['Subnets']) except ClientError as e: module.fail_json(msg=e.message, exception=traceback.format_exc(), **camel_dict_to_snake_dict(e.response)) changed = True # Security Groups if set(elb['SecurityGroups']) != set(params['SecurityGroups']): try: connection.set_security_groups( LoadBalancerArn=elb['LoadBalancerArn'], SecurityGroups=params['SecurityGroups']) except ClientError as e: module.fail_json(msg=e.message, exception=traceback.format_exc(), **camel_dict_to_snake_dict(e.response)) changed = True # Tags - only need to play with tags if tags parameter has been set to something if module.params.get("tags"): try: elb_tags = connection.describe_tags( ResourceArns=[elb['LoadBalancerArn'] ])['TagDescriptions'][0]['Tags'] except ClientError as e: module.fail_json(msg=e.message, exception=traceback.format_exc(), **camel_dict_to_snake_dict(e.response)) # Delete necessary tags tags_need_modify, tags_to_delete = compare_aws_tags( boto3_tag_list_to_ansible_dict(elb_tags), boto3_tag_list_to_ansible_dict(params['Tags']), purge_tags) if tags_to_delete: try: connection.remove_tags( ResourceArns=[elb['LoadBalancerArn']], TagKeys=tags_to_delete) except ClientError as e: module.fail_json(msg=e.message, exception=traceback.format_exc(), **camel_dict_to_snake_dict(e.response)) changed = True # Add/update tags if tags_need_modify: try: connection.add_tags(ResourceArns=[elb['LoadBalancerArn']], Tags=params['Tags']) except ClientError as e: module.fail_json(msg=e.message, exception=traceback.format_exc(), **camel_dict_to_snake_dict(e.response)) changed = True else: try: elb = connection.create_load_balancer(**params)['LoadBalancers'][0] changed = True new_load_balancer = True except ClientError as e: module.fail_json(msg=e.message, exception=traceback.format_exc(), **camel_dict_to_snake_dict(e.response)) if module.params.get("wait"): status_achieved, new_elb = wait_for_status(connection, module, elb['LoadBalancerArn'], 'active') # Now set ELB attributes. Use try statement here so we can remove the ELB if this stage fails update_attributes = [] # Get current attributes current_elb_attributes = get_elb_attributes(connection, module, elb['LoadBalancerArn']) if access_logs_enabled and current_elb_attributes[ 'access_logs_s3_enabled'] != "true": update_attributes.append({ 'Key': 'access_logs.s3.enabled', 'Value': "true" }) if not access_logs_enabled and current_elb_attributes[ 'access_logs_s3_enabled'] != "false": update_attributes.append({ 'Key': 'access_logs.s3.enabled', 'Value': 'false' }) if access_logs_s3_bucket is not None and access_logs_s3_bucket != current_elb_attributes[ 'access_logs_s3_bucket']: update_attributes.append({ 'Key': 'access_logs.s3.bucket', 'Value': access_logs_s3_bucket }) if access_logs_s3_prefix is not None and access_logs_s3_prefix != current_elb_attributes[ 'access_logs_s3_prefix']: update_attributes.append({ 'Key': 'access_logs.s3.prefix', 'Value': access_logs_s3_prefix }) if deletion_protection and current_elb_attributes[ 'deletion_protection_enabled'] != "true": update_attributes.append({ 'Key': 'deletion_protection.enabled', 'Value': "true" }) if not deletion_protection and current_elb_attributes[ 'deletion_protection_enabled'] != "false": update_attributes.append({ 'Key': 'deletion_protection.enabled', 'Value': "false" }) if idle_timeout is not None and str( idle_timeout ) != current_elb_attributes['idle_timeout_timeout_seconds']: update_attributes.append({ 'Key': 'idle_timeout.timeout_seconds', 'Value': str(idle_timeout) }) if update_attributes: try: connection.modify_load_balancer_attributes( LoadBalancerArn=elb['LoadBalancerArn'], Attributes=update_attributes) changed = True except ClientError as e: # Something went wrong setting attributes. If this ELB was created during this task, delete it to leave a consistent state if new_load_balancer: connection.delete_load_balancer( LoadBalancerArn=elb['LoadBalancerArn']) module.fail_json(msg=e.message, exception=traceback.format_exc(), **camel_dict_to_snake_dict(e.response)) # Now, if required, set ELB listeners. Use try statement here so we can remove the ELB if this stage fails try: listener_changed = create_or_update_elb_listeners( connection, module, elb) if listener_changed: changed = True except ClientError as e: # Something went wrong setting listeners. If this ELB was created during this task, delete it to leave a consistent state if new_load_balancer: connection.delete_load_balancer( LoadBalancerArn=elb['LoadBalancerArn']) module.fail_json(msg=e.message, exception=traceback.format_exc(), **camel_dict_to_snake_dict(e.response)) # Get the ELB again elb = get_elb(connection, module) # Get the ELB listeners again elb['listeners'] = get_elb_listeners(connection, module, elb['LoadBalancerArn']) # Get the ELB attributes again elb.update(get_elb_attributes(connection, module, elb['LoadBalancerArn'])) # Convert to snake_case snaked_elb = camel_dict_to_snake_dict(elb) # Get the tags of the ELB elb_tags = connection.describe_tags( ResourceArns=[elb['LoadBalancerArn']])['TagDescriptions'][0]['Tags'] snaked_elb['tags'] = boto3_tag_list_to_ansible_dict(elb_tags) module.exit_json(changed=changed, **snaked_elb)
def put_bucket_tagging(s3_client, bucket_name, tags): s3_client.put_bucket_tagging( Bucket=bucket_name, Tagging={'TagSet': ansible_dict_to_boto3_tag_list(tags)})
def create_image(module, connection): instance_id = module.params.get('instance_id') name = module.params.get('name') wait = module.params.get('wait') wait_timeout = module.params.get('wait_timeout') description = module.params.get('description') architecture = module.params.get('architecture') kernel_id = module.params.get('kernel_id') root_device_name = module.params.get('root_device_name') virtualization_type = module.params.get('virtualization_type') no_reboot = module.params.get('no_reboot') device_mapping = module.params.get('device_mapping') tags = module.params.get('tags') launch_permissions = module.params.get('launch_permissions') image_location = module.params.get('image_location') enhanced_networking = module.params.get('enhanced_networking') billing_products = module.params.get('billing_products') ramdisk_id = module.params.get('ramdisk_id') sriov_net_support = module.params.get('sriov_net_support') try: params = {'Name': name, 'Description': description} block_device_mapping = None if device_mapping: block_device_mapping = [] for device in device_mapping: device['Ebs'] = {} if 'device_name' not in device: module.fail_json( msg="Error - Device name must be set for volume.") device = rename_item_if_exists(device, 'device_name', 'DeviceName') device = rename_item_if_exists(device, 'virtual_name', 'VirtualName') device = rename_item_if_exists(device, 'no_device', 'NoDevice') device = rename_item_if_exists(device, 'volume_type', 'VolumeType', 'Ebs') device = rename_item_if_exists(device, 'snapshot_id', 'SnapshotId', 'Ebs') device = rename_item_if_exists(device, 'delete_on_termination', 'DeleteOnTermination', 'Ebs') device = rename_item_if_exists(device, 'size', 'VolumeSize', 'Ebs') device = rename_item_if_exists(device, 'volume_size', 'VolumeSize', 'Ebs') device = rename_item_if_exists(device, 'iops', 'Iops', 'Ebs') device = rename_item_if_exists(device, 'encrypted', 'Encrypted', 'Ebs') block_device_mapping.append(device) if block_device_mapping: params['BlockDeviceMappings'] = block_device_mapping if instance_id: params['InstanceId'] = instance_id params['NoReboot'] = no_reboot image_id = connection.create_image(**params).get('ImageId') else: if architecture: params['Architecture'] = architecture if virtualization_type: params['VirtualizationType'] = virtualization_type if image_location: params['ImageLocation'] = image_location if enhanced_networking: params['EnaSupport'] = enhanced_networking if billing_products: params['BillingProducts'] = billing_products if ramdisk_id: params['RamdiskId'] = ramdisk_id if sriov_net_support: params['SriovNetSupport'] = sriov_net_support if kernel_id: params['KernelId'] = kernel_id if root_device_name: params['RootDeviceName'] = root_device_name image_id = connection.register_image(**params).get('ImageId') except (botocore.exceptions.BotoCoreError, botocore.exceptions.ClientError) as e: module.fail_json_aws(e, msg="Error registering image") if wait: waiter = connection.get_waiter('image_available') delay = wait_timeout // 30 max_attempts = 30 waiter.wait(ImageIds=[image_id], WaiterConfig=dict(Delay=delay, MaxAttempts=max_attempts)) if tags: try: connection.create_tags(Resources=[image_id], Tags=ansible_dict_to_boto3_tag_list(tags)) except (botocore.exceptions.BotoCoreError, botocore.exceptions.ClientError) as e: module.fail_json_aws(e, msg="Error tagging image") if launch_permissions: try: params = dict(Attribute='LaunchPermission', ImageId=image_id, LaunchPermission=dict(Add=list())) for group_name in launch_permissions.get('group_names', []): params['LaunchPermission']['Add'].append( dict(Group=group_name)) for user_id in launch_permissions.get('user_ids', []): params['LaunchPermission']['Add'].append( dict(UserId=str(user_id))) if params['LaunchPermission']['Add']: connection.modify_image_attribute(**params) except (botocore.exceptions.BotoCoreError, botocore.exceptions.ClientError) as e: module.fail_json_aws( e, msg="Error setting launch permissions for image %s" % image_id) module.exit_json(msg="AMI creation operation complete.", changed=True, **get_ami_info( get_image_by_id(module, connection, image_id)))
def update_image(module, connection, image_id): launch_permissions = module.params.get('launch_permissions') image = get_image_by_id(module, connection, image_id) if image is None: module.fail_json(msg="Image %s does not exist" % image_id, changed=False) changed = False if launch_permissions is not None: current_permissions = image['LaunchPermissions'] current_users = set(permission['UserId'] for permission in current_permissions if 'UserId' in permission) desired_users = set( str(user_id) for user_id in launch_permissions.get('user_ids', [])) current_groups = set(permission['Group'] for permission in current_permissions if 'Group' in permission) desired_groups = set(launch_permissions.get('group_names', [])) to_add_users = desired_users - current_users to_remove_users = current_users - desired_users to_add_groups = desired_groups - current_groups to_remove_groups = current_groups - desired_groups to_add = [dict(Group=group) for group in to_add_groups ] + [dict(UserId=user_id) for user_id in to_add_users] to_remove = [dict(Group=group) for group in to_remove_groups ] + [dict(UserId=user_id) for user_id in to_remove_users] if to_add or to_remove: try: connection.modify_image_attribute(ImageId=image_id, Attribute='launchPermission', LaunchPermission=dict( Add=to_add, Remove=to_remove)) changed = True except (botocore.exceptions.BotoCoreError, botocore.exceptions.ClientError) as e: module.fail_json_aws( e, msg="Error updating launch permissions of image %s" % image_id) desired_tags = module.params.get('tags') if desired_tags is not None: current_tags = boto3_tag_list_to_ansible_dict(image.get('Tags')) tags_to_add, tags_to_remove = compare_aws_tags( current_tags, desired_tags, purge_tags=module.params.get('purge_tags')) if tags_to_remove: try: connection.delete_tags( Resources=[image_id], Tags=[dict(Key=tagkey) for tagkey in tags_to_remove]) changed = True except (botocore.exceptions.BotoCoreError, botocore.exceptions.ClientError) as e: module.fail_json_aws(e, msg="Error updating tags") if tags_to_add: try: connection.create_tags( Resources=[image_id], Tags=ansible_dict_to_boto3_tag_list(tags_to_add)) changed = True except (botocore.exceptions.BotoCoreError, botocore.exceptions.ClientError) as e: module.fail_json_aws(e, msg="Error updating tags") description = module.params.get('description') if description and description != image['Description']: try: connection.modify_image_attribute( Attribute='Description ', ImageId=image_id, Description=dict(Value=description)) changed = True except (botocore.exceptions.BotoCoreError, botocore.exceptions.ClientError) as e: module.fail_json_aws(e, msg="Error setting description for image %s" % image_id) if changed: module.exit_json(msg="AMI updated.", changed=True, **get_ami_info( get_image_by_id(module, connection, image_id))) else: module.exit_json(msg="AMI not updated.", changed=False, **get_ami_info( get_image_by_id(module, connection, image_id)))
def converge_file_system(self, name, tags, purge_tags, targets): """ Change attributes (mount targets and tags) of filesystem by name """ result = False fs_id = self.get_file_system_id(name) if tags is not None: tags_need_modify, tags_to_delete = compare_aws_tags(boto3_tag_list_to_ansible_dict(self.get_tags(FileSystemId=fs_id)), tags, purge_tags) if tags_to_delete: try: self.connection.delete_tags( FileSystemId=fs_id, TagKeys=tags_to_delete ) except ClientError as e: self.module.fail_json(msg="Unable to delete tags: {0}".format(to_native(e)), exception=traceback.format_exc(), **camel_dict_to_snake_dict(e.response)) except BotoCoreError as e: self.module.fail_json(msg="Unable to delete tags: {0}".format(to_native(e)), exception=traceback.format_exc()) result = True if tags_need_modify: try: self.connection.create_tags( FileSystemId=fs_id, Tags=ansible_dict_to_boto3_tag_list(tags_need_modify) ) except ClientError as e: self.module.fail_json(msg="Unable to create tags: {0}".format(to_native(e)), exception=traceback.format_exc(), **camel_dict_to_snake_dict(e.response)) except BotoCoreError as e: self.module.fail_json(msg="Unable to create tags: {0}".format(to_native(e)), exception=traceback.format_exc()) result = True if targets is not None: incomplete_states = [self.STATE_CREATING, self.STATE_DELETING] wait_for( lambda: len(self.get_mount_targets_in_state(fs_id, incomplete_states)), 0 ) current_targets = _index_by_key('SubnetId', self.get_mount_targets(FileSystemId=fs_id)) targets = _index_by_key('SubnetId', targets) targets_to_create, intersection, targets_to_delete = dict_diff(current_targets, targets, True) # To modify mount target it should be deleted and created again changed = [sid for sid in intersection if not targets_equal(['SubnetId', 'IpAddress', 'NetworkInterfaceId'], current_targets[sid], targets[sid])] targets_to_delete = list(targets_to_delete) + changed targets_to_create = list(targets_to_create) + changed if targets_to_delete: for sid in targets_to_delete: self.connection.delete_mount_target( MountTargetId=current_targets[sid]['MountTargetId'] ) wait_for( lambda: len(self.get_mount_targets_in_state(fs_id, incomplete_states)), 0 ) result = True if targets_to_create: for sid in targets_to_create: self.connection.create_mount_target( FileSystemId=fs_id, **targets[sid] ) wait_for( lambda: len(self.get_mount_targets_in_state(fs_id, incomplete_states)), 0, self.wait_timeout ) result = True # If no security groups were passed into the module, then do not change it. security_groups_to_update = [sid for sid in intersection if 'SecurityGroups' in targets[sid] and current_targets[sid]['SecurityGroups'] != targets[sid]['SecurityGroups']] if security_groups_to_update: for sid in security_groups_to_update: self.connection.modify_mount_target_security_groups( MountTargetId=current_targets[sid]['MountTargetId'], SecurityGroups=targets[sid].get('SecurityGroups', None) ) result = True return result
def create_image(module, connection): instance_id = module.params.get('instance_id') name = module.params.get('name') wait = module.params.get('wait') wait_timeout = module.params.get('wait_timeout') description = module.params.get('description') architecture = module.params.get('architecture') kernel_id = module.params.get('kernel_id') root_device_name = module.params.get('root_device_name') virtualization_type = module.params.get('virtualization_type') no_reboot = module.params.get('no_reboot') device_mapping = module.params.get('device_mapping') tags = module.params.get('tags') launch_permissions = module.params.get('launch_permissions') image_location = module.params.get('image_location') enhanced_networking = module.params.get('enhanced_networking') billing_products = module.params.get('billing_products') ramdisk_id = module.params.get('ramdisk_id') sriov_net_support = module.params.get('sriov_net_support') try: params = { 'Name': name, 'Description': description } block_device_mapping = None if device_mapping: block_device_mapping = [] for device in device_mapping: device['Ebs'] = {} if 'device_name' not in device: module.fail_json(msg="Error - Device name must be set for volume.") device = rename_item_if_exists(device, 'device_name', 'DeviceName') device = rename_item_if_exists(device, 'virtual_name', 'VirtualName') device = rename_item_if_exists(device, 'no_device', 'NoDevice') device = rename_item_if_exists(device, 'volume_type', 'VolumeType', 'Ebs') device = rename_item_if_exists(device, 'snapshot_id', 'SnapshotId', 'Ebs') device = rename_item_if_exists(device, 'delete_on_termination', 'DeleteOnTermination', 'Ebs') device = rename_item_if_exists(device, 'size', 'VolumeSize', 'Ebs') device = rename_item_if_exists(device, 'volume_size', 'VolumeSize', 'Ebs') device = rename_item_if_exists(device, 'iops', 'Iops', 'Ebs') device = rename_item_if_exists(device, 'encrypted', 'Encrypted', 'Ebs') block_device_mapping.append(device) if block_device_mapping: params['BlockDeviceMappings'] = block_device_mapping if instance_id: params['InstanceId'] = instance_id params['NoReboot'] = no_reboot image_id = connection.create_image(**params).get('ImageId') else: if architecture: params['Architecture'] = architecture if virtualization_type: params['VirtualizationType'] = virtualization_type if image_location: params['ImageLocation'] = image_location if enhanced_networking: params['EnaSupport'] = enhanced_networking if billing_products: params['BillingProducts'] = billing_products if ramdisk_id: params['RamdiskId'] = ramdisk_id if sriov_net_support: params['SriovNetSupport'] = sriov_net_support if kernel_id: params['KernelId'] = kernel_id if root_device_name: params['RootDeviceName'] = root_device_name image_id = connection.register_image(**params).get('ImageId') except (botocore.exceptions.BotoCoreError, botocore.exceptions.ClientError) as e: module.fail_json_aws(e, msg="Error registering image") if wait: waiter = connection.get_waiter('image_available') delay = wait_timeout // 30 max_attempts = 30 waiter.wait(ImageIds=[image_id], WaiterConfig=dict(Delay=delay, MaxAttempts=max_attempts)) if tags: try: connection.create_tags(Resources=[image_id], Tags=ansible_dict_to_boto3_tag_list(tags)) except (botocore.exceptions.BotoCoreError, botocore.exceptions.ClientError) as e: module.fail_json_aws(e, msg="Error tagging image") if launch_permissions: try: params = dict(Attribute='LaunchPermission', ImageId=image_id, LaunchPermission=dict(Add=list())) for group_name in launch_permissions.get('group_names', []): params['LaunchPermission']['Add'].append(dict(Group=group_name)) for user_id in launch_permissions.get('user_ids', []): params['LaunchPermission']['Add'].append(dict(UserId=str(user_id))) if params['LaunchPermission']['Add']: connection.modify_image_attribute(**params) except (botocore.exceptions.BotoCoreError, botocore.exceptions.ClientError) as e: module.fail_json_aws(e, msg="Error setting launch permissions for image %s" % image_id) module.exit_json(msg="AMI creation operation complete.", changed=True, **get_ami_info(get_image_by_id(module, connection, image_id)))