def modify_elb_attributes(self): """ Update ELB attributes if required :return: """ update_attributes = [] if self.access_logs_enabled and self.elb_attributes['access_logs_s3_enabled'] != "true": update_attributes.append({'Key': 'access_logs.s3.enabled', 'Value': "true"}) if not self.access_logs_enabled and self.elb_attributes['access_logs_s3_enabled'] != "false": update_attributes.append({'Key': 'access_logs.s3.enabled', 'Value': 'false'}) if self.access_logs_s3_bucket is not None and self.access_logs_s3_bucket != self.elb_attributes['access_logs_s3_bucket']: update_attributes.append({'Key': 'access_logs.s3.bucket', 'Value': self.access_logs_s3_bucket}) if self.access_logs_s3_prefix is not None and self.access_logs_s3_prefix != self.elb_attributes['access_logs_s3_prefix']: update_attributes.append({'Key': 'access_logs.s3.prefix', 'Value': self.access_logs_s3_prefix}) if self.deletion_protection and self.elb_attributes['deletion_protection_enabled'] != "true": update_attributes.append({'Key': 'deletion_protection.enabled', 'Value': "true"}) if self.deletion_protection is not None and not self.deletion_protection and self.elb_attributes['deletion_protection_enabled'] != "false": update_attributes.append({'Key': 'deletion_protection.enabled', 'Value': "false"}) if self.idle_timeout is not None and str(self.idle_timeout) != self.elb_attributes['idle_timeout_timeout_seconds']: update_attributes.append({'Key': 'idle_timeout.timeout_seconds', 'Value': str(self.idle_timeout)}) if update_attributes: try: AWSRetry.jittered_backoff()( self.connection.modify_load_balancer_attributes )(LoadBalancerArn=self.elb['LoadBalancerArn'], Attributes=update_attributes) self.changed = True except (BotoCoreError, ClientError) as e: # Something went wrong setting attributes. If this ELB was created during this task, delete it to leave a consistent state if self.new_load_balancer: AWSRetry.jittered_backoff()(self.connection.delete_load_balancer)(LoadBalancerArn=self.elb['LoadBalancerArn']) self.module.fail_json_aws(e)
def modify(self): try: # Rules is not a valid parameter for modify_listener if 'Rules' in self.listener: self.listener.pop('Rules') AWSRetry.jittered_backoff()(self.connection.modify_listener)(**self.listener) except (BotoCoreError, ClientError) as e: self.module.fail_json_aws(e)
def add(self): try: # Rules is not a valid parameter for create_listener if 'Rules' in self.listener: self.listener.pop('Rules') AWSRetry.jittered_backoff()(self.connection.create_listener)(LoadBalancerArn=self.elb_arn, **self.listener) except (BotoCoreError, ClientError) as e: self.module.fail_json_aws(e)
def delete(self): """ Delete a listener rule :return: """ try: AWSRetry.jittered_backoff()(self.connection.delete_rule)(RuleArn=self.rule['RuleArn']) except (BotoCoreError, ClientError) as e: self.module.fail_json_aws(e) self.changed = True
def modify_security_groups(self): """ Modify elb security groups to match module parameters :return: """ try: AWSRetry.jittered_backoff()( self.connection.set_security_groups )(LoadBalancerArn=self.elb['LoadBalancerArn'], SecurityGroups=self.security_groups) except (BotoCoreError, ClientError) as e: self.module.fail_json_aws(e) self.changed = True
def delete(self): """ Delete elb :return: """ try: AWSRetry.jittered_backoff()( self.connection.delete_load_balancer )(LoadBalancerArn=self.elb['LoadBalancerArn']) except (BotoCoreError, ClientError) as e: self.module.fail_json_aws(e) self.changed = True
def modify(self): """ Modify a listener rule :return: """ try: del self.rule['Priority'] AWSRetry.jittered_backoff()(self.connection.modify_rule)(**self.rule) except (BotoCoreError, ClientError) as e: self.module.fail_json_aws(e) self.changed = True
def modify_tags(self): """ Modify elb tags :return: """ try: AWSRetry.jittered_backoff()( self.connection.add_tags )(ResourceArns=[self.elb['LoadBalancerArn']], Tags=self.tags) except (BotoCoreError, ClientError) as e: self.module.fail_json_aws(e) self.changed = True
def delete_tags(self, tags_to_delete): """ Delete elb tags :return: """ try: AWSRetry.jittered_backoff()( self.connection.remove_tags )(ResourceArns=[self.elb['LoadBalancerArn']], TagKeys=tags_to_delete) except (BotoCoreError, ClientError) as e: self.module.fail_json_aws(e) self.changed = True
def create(self): """ Create a listener rule :return: """ try: self.rule['ListenerArn'] = self.listener_arn self.rule['Priority'] = int(self.rule['Priority']) AWSRetry.jittered_backoff()(self.connection.create_rule)(**self.rule) except (BotoCoreError, ClientError) as e: self.module.fail_json_aws(e) self.changed = True
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 __init__(self, connection, connection_ec2, module): """ :param connection: boto3 connection :param module: Ansible module """ super(ApplicationLoadBalancer, self).__init__(connection, module) self.connection_ec2 = connection_ec2 # Ansible module parameters specific to ALBs self.type = 'application' if module.params.get('security_groups') is not None: try: self.security_groups = AWSRetry.jittered_backoff()( get_ec2_security_group_ids_from_names )(module.params.get('security_groups'), self.connection_ec2, boto3=True) except ValueError as e: self.module.fail_json(msg=str(e), exception=traceback.format_exc()) except (BotoCoreError, ClientError) as e: self.module.fail_json_aws(e) else: self.security_groups = module.params.get('security_groups') self.access_logs_enabled = module.params.get("access_logs_enabled") self.access_logs_s3_bucket = module.params.get("access_logs_s3_bucket") self.access_logs_s3_prefix = module.params.get("access_logs_s3_prefix") self.idle_timeout = module.params.get("idle_timeout")
def get_elb_listener(connection, module, elb_arn, listener_port): """ Get an ELB listener based on the port provided. If not found, return None. :param connection: AWS boto3 elbv2 connection :param module: Ansible module :param elb_arn: ARN of the ELB to look at :param listener_port: Port of the listener to look for :return: boto3 ELB listener dict or None if not found """ try: listener_paginator = connection.get_paginator('describe_listeners') listeners = (AWSRetry.jittered_backoff()(listener_paginator.paginate)(LoadBalancerArn=elb_arn).build_full_result())['Listeners'] except (BotoCoreError, ClientError) as e: module.fail_json_aws(e) l = None for listener in listeners: if listener['Port'] == listener_port: l = listener break return l
def create_elb(self): """ Create a load balancer :return: """ # Required parameters params = dict() params['Name'] = self.name params['Type'] = self.type # Other parameters if self.subnets is not None: params['Subnets'] = self.subnets params['Scheme'] = self.scheme if self.tags is not None: params['Tags'] = self.tags try: self.elb = AWSRetry.jittered_backoff()(self.connection.create_load_balancer)(**params)['LoadBalancers'][0] self.changed = True self.new_load_balancer = True except (BotoCoreError, ClientError) as e: self.module.fail_json_aws(e) if self.wait: self.wait_for_status(self.elb['LoadBalancerArn'])
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 _get_elb_listeners(self): """ Get ELB listeners :return: """ try: listener_paginator = self.connection.get_paginator('describe_listeners') return (AWSRetry.jittered_backoff()(listener_paginator.paginate)(LoadBalancerArn=self.elb_arn).build_full_result())['Listeners'] except (BotoCoreError, ClientError) as e: self.module.fail_json_aws(e)
def get_elb_tags(self): """ Get load balancer tags :return: """ try: return AWSRetry.jittered_backoff()( self.connection.describe_tags )(ResourceArns=[self.elb['LoadBalancerArn']])['TagDescriptions'][0]['Tags'] except (BotoCoreError, ClientError) as e: self.module.fail_json_aws(e)
def get_elb_listener_rules(connection, module, listener_arn): """ Get rules for a particular ELB listener using the listener ARN. :param connection: AWS boto3 elbv2 connection :param module: Ansible module :param listener_arn: ARN of the ELB listener :return: boto3 ELB rules list """ try: return AWSRetry.jittered_backoff()(connection.describe_rules)(ListenerArn=listener_arn)['Rules'] except (BotoCoreError, ClientError) as e: module.fail_json_aws(e)
def modify_elb_attributes(self): """ Update ELB attributes if required :return: """ update_attributes = [] if self.deletion_protection and self.elb_attributes['deletion_protection_enabled'] != "true": update_attributes.append({'Key': 'deletion_protection.enabled', 'Value': "true"}) if self.deletion_protection is not None and not self.deletion_protection and self.elb_attributes['deletion_protection_enabled'] != "false": update_attributes.append({'Key': 'deletion_protection.enabled', 'Value': "false"}) if update_attributes: try: AWSRetry.jittered_backoff()( self.connection.modify_load_balancer_attributes )(LoadBalancerArn=self.elb['LoadBalancerArn'], Attributes=update_attributes) self.changed = True except (BotoCoreError, ClientError) as e: # Something went wrong setting attributes. If this ELB was created during this task, delete it to leave a consistent state if self.new_load_balancer: AWSRetry.jittered_backoff()(self.connection.delete_load_balancer)(LoadBalancerArn=self.elb['LoadBalancerArn']) self.module.fail_json_aws(e)
def attach_vgw(client, module, vpn_gateway_id): params = dict() params['VpcId'] = module.params.get('vpc_id') try: response = AWSRetry.jittered_backoff()(client.attach_vpn_gateway)(VpnGatewayId=vpn_gateway_id, VpcId=params['VpcId']) except botocore.exceptions.ClientError as e: module.fail_json(msg=to_native(e), exception=traceback.format_exc()) status_achieved, vgw = wait_for_status(client, module, [vpn_gateway_id], 'attached') if not status_achieved: module.fail_json(msg='Error waiting for vpc to attach to vgw - please check the AWS console') result = response return result
def __init__(self, module): self.module = module try: region, ec2_url, aws_connect_kwargs = get_aws_connection_info(module, boto3=True) self.client = boto3_conn(module, conn_type='client', resource='cloudformation', region=region, endpoint=ec2_url, **aws_connect_kwargs) backoff_wrapper = AWSRetry.jittered_backoff(retries=10, delay=3, max_delay=30) self.client.describe_stacks = backoff_wrapper(self.client.describe_stacks) self.client.list_stack_resources = backoff_wrapper(self.client.list_stack_resources) self.client.describe_stack_events = backoff_wrapper(self.client.describe_stack_events) self.client.get_stack_policy = backoff_wrapper(self.client.get_stack_policy) self.client.get_template = backoff_wrapper(self.client.get_template) except botocore.exceptions.NoRegionError: self.module.fail_json(msg="Region must be specified as a parameter, in AWS_DEFAULT_REGION environment variable or in boto configuration file") except Exception as e: self.module.fail_json(msg="Can't establish connection - " + str(e), exception=traceback.format_exc())
def create_connection(client, location, bandwidth, name, lag_id): if not name: raise DirectConnectError(msg="Failed to create a Direct Connect connection: name required.") params = { 'location': location, 'bandwidth': bandwidth, 'connectionName': name, } if lag_id: params['lagId'] = lag_id try: connection = AWSRetry.backoff(**retry_params)(client.create_connection)(**params) except (BotoCoreError, ClientError) as e: raise DirectConnectError(msg="Failed to create DirectConnect connection {0}".format(name), last_traceback=traceback.format_exc(), exception=e) return connection['connectionId']
def get_elb_attributes(self): """ Get load balancer attributes :return: """ try: attr_list = AWSRetry.jittered_backoff()( self.connection.describe_load_balancer_attributes )(LoadBalancerArn=self.elb['LoadBalancerArn'])['Attributes'] elb_attributes = boto3_tag_list_to_ansible_dict(attr_list) except (BotoCoreError, ClientError) as e: self.module.fail_json_aws(e) # Replace '.' with '_' in attribute key names to make it more Ansibley return dict((k.replace('.', '_'), v) for k, v in elb_attributes.items())
def convert_tg_name_to_arn(connection, module, tg_name): """ Get ARN of a target group using the target group's name :param connection: AWS boto3 elbv2 connection :param module: Ansible module :param tg_name: Name of the target group :return: target group ARN string """ try: response = AWSRetry.jittered_backoff()(connection.describe_target_groups)(Names=[tg_name]) except (BotoCoreError, ClientError) as e: module.fail_json_aws(e) tg_arn = response['TargetGroups'][0]['TargetGroupArn'] return tg_arn
def get_vpc(module, connection, vpc_id): # wait for vpc to be available try: connection.get_waiter('vpc_available').wait(VpcIds=[vpc_id]) except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: module.fail_json_aws(e, msg="Unable to wait for VPC {0} to be available.".format(vpc_id)) try: vpc_obj = AWSRetry.backoff( delay=3, tries=8, catch_extra_error_codes=['InvalidVpcID.NotFound'], )(connection.describe_vpcs)(VpcIds=[vpc_id])['Vpcs'][0] except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: module.fail_json_aws(e, msg="Failed to describe VPCs") try: vpc_obj['ClassicLinkEnabled'] = get_classic_link_with_backoff(connection, vpc_id) except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: module.fail_json_aws(e, msg="Failed to describe VPCs") return vpc_obj
def list_elbs(self): elb_array, token = [], None get_elb_with_backoff = AWSRetry.backoff(tries=5, delay=5, backoff=2.0)(self.connection.get_all_load_balancers) while True: all_elbs = get_elb_with_backoff(marker=token) token = all_elbs.next_marker if all_elbs: if self.names: for existing_lb in all_elbs: if existing_lb.name in self.names: elb_array.append(existing_lb) else: elb_array.extend(all_elbs) else: break if token is None: break return list(map(self._get_elb_info, elb_array))
def connection_exists(client, connection_id=None, connection_name=None, verify=True): params = {} if connection_id: params['connectionId'] = connection_id try: response = AWSRetry.backoff(**retry_params)(client.describe_connections)(**params) except (BotoCoreError, ClientError) as e: if connection_id: msg = "Failed to describe DirectConnect ID {0}".format(connection_id) else: msg = "Failed to describe DirectConnect connections" raise DirectConnectError(msg=msg, last_traceback=traceback.format_exc(), exception=e) match = [] connection = [] # look for matching connections if len(response.get('connections', [])) == 1 and connection_id: if response['connections'][0]['connectionState'] != 'deleted': match.append(response['connections'][0]['connectionId']) connection.extend(response['connections']) for conn in response.get('connections', []): if connection_name == conn['connectionName'] and conn['connectionState'] != 'deleted': match.append(conn['connectionId']) connection.append(conn) # verifying if the connections exists; if true, return connection identifier, otherwise return False if verify and len(match) == 1: return match[0] elif verify: return False # not verifying if the connection exists; just return current connection info elif len(connection) == 1: return {'connection': connection[0]} return {'connection': {}}
def main(): module = AnsibleAWSModule( argument_spec={ 'identity': dict(required=True, type='str'), 'state': dict(default='present', choices=['present', 'absent']), 'policy_name': dict(required=True, type='str'), 'policy': dict(type='json', default=None), }, required_if=[['state', 'present', ['policy']]], supports_check_mode=True, ) # SES APIs seem to have a much lower throttling threshold than most of the rest of the AWS APIs. # Docs say 1 call per second. This shouldn't actually be a big problem for normal usage, but # the ansible build runs multiple instances of the test in parallel that's caused throttling # failures so apply a jittered backoff to call SES calls. connection = module.client('ses', retry_decorator=AWSRetry.jittered_backoff()) state = module.params.get("state") if state == 'present': create_or_update_identity_policy(connection, module) else: delete_identity_policy(connection, module)
def main(): argument_spec = ansible.module_utils.ec2.ec2_argument_spec() argument_spec.update(dict( stack_name=dict(required=True), template_parameters=dict(required=False, type='dict', default={}), state=dict(default='present', choices=['present', 'absent']), template=dict(default=None, required=False, type='path'), notification_arns=dict(default=None, required=False), stack_policy=dict(default=None, required=False), disable_rollback=dict(default=False, type='bool'), template_url=dict(default=None, required=False), template_format=dict(default=None, choices=['json', 'yaml'], required=False), create_changeset=dict(default=False, type='bool'), changeset_name=dict(default=None, required=False), role_arn=dict(default=None, required=False), tags=dict(default=None, type='dict') ) ) module = AnsibleModule( argument_spec=argument_spec, mutually_exclusive=[['template_url', 'template']], supports_check_mode=True ) if not HAS_BOTO3: module.fail_json(msg='boto3 and botocore are required for this module') # collect the parameters that are passed to boto3. Keeps us from having so many scalars floating around. stack_params = { 'Capabilities': ['CAPABILITY_IAM', 'CAPABILITY_NAMED_IAM'], } state = module.params['state'] stack_params['StackName'] = module.params['stack_name'] if module.params['template'] is not None: stack_params['TemplateBody'] = open(module.params['template'], 'r').read() elif module.params['template_url'] is not None: stack_params['TemplateURL'] = module.params['template_url'] if module.params.get('notification_arns'): stack_params['NotificationARNs'] = module.params['notification_arns'].split(',') else: stack_params['NotificationARNs'] = [] if module.params['stack_policy'] is not None: stack_params['StackPolicyBody'] = open(module.params['stack_policy'], 'r').read() if module.params['changeset_name'] is not None: stack_params['ChangeSetName'] = module.params['changeset_name'] template_parameters = module.params['template_parameters'] stack_params['Parameters'] = [{'ParameterKey':k, 'ParameterValue':str(v)} for k, v in template_parameters.items()] if isinstance(module.params.get('tags'), dict): stack_params['Tags'] = ansible.module_utils.ec2.ansible_dict_to_boto3_tag_list(module.params['tags']) if module.params.get('role_arn'): stack_params['RoleARN'] = module.params['role_arn'] result = {} try: region, ec2_url, aws_connect_kwargs = ansible.module_utils.ec2.get_aws_connection_info(module, boto3=True) cfn = ansible.module_utils.ec2.boto3_conn(module, conn_type='client', resource='cloudformation', region=region, endpoint=ec2_url, **aws_connect_kwargs) except botocore.exceptions.NoCredentialsError as e: module.fail_json(msg=boto_exception(e)) # Wrap the cloudformation client methods that this module uses with # automatic backoff / retry for throttling error codes backoff_wrapper = AWSRetry.jittered_backoff(retries=10, delay=3, max_delay=30) cfn.describe_stack_events = backoff_wrapper(cfn.describe_stack_events) cfn.create_stack = backoff_wrapper(cfn.create_stack) cfn.list_change_sets = backoff_wrapper(cfn.list_change_sets) cfn.create_change_set = backoff_wrapper(cfn.create_change_set) cfn.update_stack = backoff_wrapper(cfn.update_stack) cfn.describe_stacks = backoff_wrapper(cfn.describe_stacks) cfn.list_stack_resources = backoff_wrapper(cfn.list_stack_resources) cfn.delete_stack = backoff_wrapper(cfn.delete_stack) stack_info = get_stack_facts(cfn, stack_params['StackName']) if module.check_mode: if state == 'absent' and stack_info: module.exit_json(changed=True, msg='Stack would be deleted', meta=[]) elif state == 'absent' and not stack_info: module.exit_json(changed=False, msg='Stack doesn\'t exist', meta=[]) elif state == 'present' and not stack_info: module.exit_json(changed=True, msg='New stack would be created', meta=[]) else: module.exit_json(**check_mode_changeset(module, stack_params, cfn)) if state == 'present': if not stack_info: result = create_stack(module, stack_params, cfn) elif module.params.get('create_changeset'): result = create_changeset(module, stack_params, cfn) else: result = update_stack(module, stack_params, cfn) # format the stack output stack = get_stack_facts(cfn, stack_params['StackName']) if result.get('stack_outputs') is None: # always define stack_outputs, but it may be empty result['stack_outputs'] = {} for output in stack.get('Outputs', []): result['stack_outputs'][output['OutputKey']] = output['OutputValue'] stack_resources = [] reslist = cfn.list_stack_resources(StackName=stack_params['StackName']) for res in reslist.get('StackResourceSummaries', []): stack_resources.append({ "logical_resource_id": res['LogicalResourceId'], "physical_resource_id": res.get('PhysicalResourceId', ''), "resource_type": res['ResourceType'], "last_updated_time": res['LastUpdatedTimestamp'], "status": res['ResourceStatus'], "status_reason": res.get('ResourceStatusReason') # can be blank, apparently }) result['stack_resources'] = stack_resources elif state == 'absent': # absent state is different because of the way delete_stack works. # problem is it it doesn't give an error if stack isn't found # so must describe the stack first try: stack = get_stack_facts(cfn, stack_params['StackName']) if not stack: result = {'changed': False, 'output': 'Stack not found.'} else: cfn.delete_stack(StackName=stack_params['StackName']) result = stack_operation(cfn, stack_params['StackName'], 'DELETE') except Exception as err: module.fail_json(msg=boto_exception(err), exception=traceback.format_exc()) if module.params['template_format'] is not None: result['warnings'] = [('Argument `template_format` is deprecated ' 'since Ansible 2.3, JSON and YAML templates are now passed ' 'directly to the CloudFormation API.')] module.exit_json(**result)
def main(): module = AnsibleAWSModule( argument_spec={ 'name': dict(type='str', required=True), 'state': dict(type='str', choices=['present', 'absent'], default='present'), 'description': dict(type='str'), 'scope': dict(type='dict'), 'source': dict(type='dict', required=True), 'input_parameters': dict(type='str'), 'execution_frequency': dict(type='str', choices=[ 'One_Hour', 'Three_Hours', 'Six_Hours', 'Twelve_Hours', 'TwentyFour_Hours' ]), }, supports_check_mode=False, ) result = {'changed': False} name = module.params.get('name') resource_type = module.params.get('resource_type') state = module.params.get('state') params = {} if name: params['ConfigRuleName'] = name if module.params.get('description'): params['Description'] = module.params.get('description') if module.params.get('scope'): params['Scope'] = {} if module.params.get('scope').get('compliance_types'): params['Scope'].update({ 'ComplianceResourceTypes': module.params.get('scope').get('compliance_types') }) if module.params.get('scope').get('tag_key'): params['Scope'].update( {'TagKey': module.params.get('scope').get('tag_key')}) if module.params.get('scope').get('tag_value'): params['Scope'].update( {'TagValue': module.params.get('scope').get('tag_value')}) if module.params.get('scope').get('compliance_id'): params['Scope'].update({ 'ComplianceResourceId': module.params.get('scope').get('compliance_id') }) if module.params.get('source'): params['Source'] = {} if module.params.get('source').get('owner'): params['Source'].update( {'Owner': module.params.get('source').get('owner')}) if module.params.get('source').get('identifier'): params['Source'].update({ 'SourceIdentifier': module.params.get('source').get('identifier') }) if module.params.get('source').get('details'): params['Source'].update( {'SourceDetails': module.params.get('source').get('details')}) if module.params.get('input_parameters'): params['InputParameters'] = module.params.get('input_parameters') if module.params.get('execution_frequency'): params['MaximumExecutionFrequency'] = module.params.get( 'execution_frequency') params['ConfigRuleState'] = 'ACTIVE' client = module.client('config', retry_decorator=AWSRetry.jittered_backoff()) existing_rule = rule_exists(client, module, params) if state == 'present': if not existing_rule: create_resource(client, module, params, result) else: update_resource(client, module, params, result) if state == 'absent': if existing_rule: delete_resource(client, module, params, result) module.exit_json(**result)
def main(): argument_spec = ansible.module_utils.ec2.ec2_argument_spec() argument_spec.update( dict( stack_name=dict(required=True), template_parameters=dict(required=False, type='dict', default={}), state=dict(default='present', choices=['present', 'absent']), template=dict(default=None, required=False, type='path'), notification_arns=dict(default=None, required=False), stack_policy=dict(default=None, required=False), disable_rollback=dict(default=False, type='bool'), create_timeout=dict(default=None, type='int'), template_url=dict(default=None, required=False), template_body=dict(default=None, require=False), template_format=dict(default=None, choices=['json', 'yaml'], required=False), create_changeset=dict(default=False, type='bool'), changeset_name=dict(default=None, required=False), role_arn=dict(default=None, required=False), tags=dict(default=None, type='dict'), termination_protection=dict(default=None, type='bool'), events_limit=dict(default=200, type='int'), )) module = AnsibleModule( argument_spec=argument_spec, mutually_exclusive=[['template_url', 'template', 'template_body']], supports_check_mode=True) if not HAS_BOTO3: module.fail_json(msg='boto3 and botocore are required for this module') # collect the parameters that are passed to boto3. Keeps us from having so many scalars floating around. stack_params = { 'Capabilities': ['CAPABILITY_IAM', 'CAPABILITY_NAMED_IAM'], 'ClientRequestToken': to_native(uuid.uuid4()), } state = module.params['state'] stack_params['StackName'] = module.params['stack_name'] if module.params['template'] is not None: stack_params['TemplateBody'] = open(module.params['template'], 'r').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'] if module.params.get('notification_arns'): stack_params['NotificationARNs'] = module.params[ 'notification_arns'].split(',') else: stack_params['NotificationARNs'] = [] # can't check the policy when verifying. if module.params[ 'stack_policy'] is not None and not module.check_mode and not module.params[ 'create_changeset']: stack_params['StackPolicyBody'] = open(module.params['stack_policy'], 'r').read() template_parameters = module.params['template_parameters'] stack_params['Parameters'] = [] for k, v in template_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'] = str(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 isinstance(module.params.get('tags'), dict): stack_params[ 'Tags'] = ansible.module_utils.ec2.ansible_dict_to_boto3_tag_list( module.params['tags']) if module.params.get('role_arn'): stack_params['RoleARN'] = module.params['role_arn'] result = {} try: region, ec2_url, aws_connect_kwargs = ansible.module_utils.ec2.get_aws_connection_info( module, boto3=True) cfn = ansible.module_utils.ec2.boto3_conn(module, conn_type='client', resource='cloudformation', region=region, endpoint=ec2_url, **aws_connect_kwargs) except botocore.exceptions.NoCredentialsError as e: module.fail_json(msg=boto_exception(e)) # Wrap the cloudformation client methods that this module uses with # automatic backoff / retry for throttling error codes backoff_wrapper = AWSRetry.jittered_backoff(retries=10, delay=3, max_delay=30) cfn.describe_stack_events = backoff_wrapper(cfn.describe_stack_events) cfn.create_stack = backoff_wrapper(cfn.create_stack) cfn.list_change_sets = backoff_wrapper(cfn.list_change_sets) cfn.create_change_set = backoff_wrapper(cfn.create_change_set) cfn.update_stack = backoff_wrapper(cfn.update_stack) cfn.describe_stacks = backoff_wrapper(cfn.describe_stacks) cfn.list_stack_resources = backoff_wrapper(cfn.list_stack_resources) cfn.delete_stack = backoff_wrapper(cfn.delete_stack) if boto_supports_termination_protection(cfn): cfn.update_termination_protection = backoff_wrapper( cfn.update_termination_protection) stack_info = get_stack_facts(cfn, stack_params['StackName']) if module.check_mode: if state == 'absent' and stack_info: module.exit_json(changed=True, msg='Stack would be deleted', meta=[]) elif state == 'absent' and not stack_info: module.exit_json(changed=False, msg='Stack doesn\'t exist', meta=[]) elif state == 'present' and not stack_info: module.exit_json(changed=True, msg='New stack would be created', meta=[]) else: module.exit_json(**check_mode_changeset(module, stack_params, cfn)) if state == 'present': if not stack_info: result = create_stack(module, stack_params, cfn, module.params.get('events_limit')) elif module.params.get('create_changeset'): result = create_changeset(module, stack_params, cfn, module.params.get('events_limit')) else: if module.params.get('termination_protection') is not None: update_termination_protection( module, cfn, stack_params['StackName'], bool(module.params.get('termination_protection'))) result = update_stack(module, stack_params, cfn, module.params.get('events_limit')) # format the stack output stack = get_stack_facts(cfn, stack_params['StackName']) if result.get('stack_outputs') is None: # always define stack_outputs, but it may be empty result['stack_outputs'] = {} for output in stack.get('Outputs', []): result['stack_outputs'][ output['OutputKey']] = output['OutputValue'] stack_resources = [] reslist = cfn.list_stack_resources(StackName=stack_params['StackName']) for res in reslist.get('StackResourceSummaries', []): stack_resources.append({ "logical_resource_id": res['LogicalResourceId'], "physical_resource_id": res.get('PhysicalResourceId', ''), "resource_type": res['ResourceType'], "last_updated_time": res['LastUpdatedTimestamp'], "status": res['ResourceStatus'], "status_reason": res.get('ResourceStatusReason') # can be blank, apparently }) result['stack_resources'] = stack_resources elif state == 'absent': # absent state is different because of the way delete_stack works. # problem is it it doesn't give an error if stack isn't found # so must describe the stack first try: stack = get_stack_facts(cfn, stack_params['StackName']) if not stack: result = {'changed': False, 'output': 'Stack not found.'} else: if stack_params.get('RoleARN') is None: cfn.delete_stack(StackName=stack_params['StackName']) else: cfn.delete_stack(StackName=stack_params['StackName'], RoleARN=stack_params['RoleARN']) result = stack_operation( cfn, stack_params['StackName'], 'DELETE', module.params.get('events_limit'), stack_params.get('ClientRequestToken', None)) except Exception as err: module.fail_json(msg=boto_exception(err), exception=traceback.format_exc()) if module.params['template_format'] is not None: result['warnings'] = [ ('Argument `template_format` is deprecated ' 'since Ansible 2.3, JSON and YAML templates are now passed ' 'directly to the CloudFormation API.') ] module.exit_json(**result)
def main(): argument_spec = dict( name=dict(required=True), cidr_block=dict(type='list', required=True), ipv6_cidr=dict(type='bool', default=False), tenancy=dict(choices=['default', 'dedicated'], default='default'), dns_support=dict(type='bool', default=True), dns_hostnames=dict(type='bool', default=True), dhcp_opts_id=dict(), tags=dict(type='dict', aliases=['resource_tags']), state=dict(choices=['present', 'absent'], default='present'), multi_ok=dict(type='bool', default=False), purge_cidrs=dict(type='bool', default=False), ) module = AnsibleAWSModule(argument_spec=argument_spec, supports_check_mode=True) name = module.params.get('name') cidr_block = get_cidr_network_bits(module, module.params.get('cidr_block')) ipv6_cidr = module.params.get('ipv6_cidr') purge_cidrs = module.params.get('purge_cidrs') tenancy = module.params.get('tenancy') dns_support = module.params.get('dns_support') dns_hostnames = module.params.get('dns_hostnames') dhcp_id = module.params.get('dhcp_opts_id') tags = module.params.get('tags') state = module.params.get('state') multi = module.params.get('multi_ok') changed = False connection = module.client( 'ec2', retry_decorator=AWSRetry.jittered_backoff( retries=8, delay=3, catch_extra_error_codes=['InvalidVpcID.NotFound'])) if dns_hostnames and not dns_support: module.fail_json( msg= 'In order to enable DNS Hostnames you must also enable DNS support' ) if state == 'present': # Check if VPC exists vpc_id = vpc_exists(module, connection, name, cidr_block, multi) if vpc_id is None: vpc_id = create_vpc(connection, module, cidr_block[0], tenancy) changed = True vpc_obj = get_vpc(module, connection, vpc_id) associated_cidrs = dict( (cidr['CidrBlock'], cidr['AssociationId']) for cidr in vpc_obj.get('CidrBlockAssociationSet', []) if cidr['CidrBlockState']['State'] != 'disassociated') to_add = [cidr for cidr in cidr_block if cidr not in associated_cidrs] to_remove = [ associated_cidrs[cidr] for cidr in associated_cidrs if cidr not in cidr_block ] expected_cidrs = [ cidr for cidr in associated_cidrs if associated_cidrs[cidr] not in to_remove ] + to_add if len(cidr_block) > 1: for cidr in to_add: changed = True try: connection.associate_vpc_cidr_block(CidrBlock=cidr, VpcId=vpc_id) except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: module.fail_json_aws( e, "Unable to associate CIDR {0}.".format(ipv6_cidr)) if ipv6_cidr: if 'Ipv6CidrBlockAssociationSet' in vpc_obj.keys(): module.warn( "Only one IPv6 CIDR is permitted per VPC, {0} already has CIDR {1}" .format( vpc_id, vpc_obj['Ipv6CidrBlockAssociationSet'][0] ['Ipv6CidrBlock'])) else: try: connection.associate_vpc_cidr_block( AmazonProvidedIpv6CidrBlock=ipv6_cidr, VpcId=vpc_id) changed = True except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: module.fail_json_aws( e, "Unable to associate CIDR {0}.".format(ipv6_cidr)) if purge_cidrs: for association_id in to_remove: changed = True try: connection.disassociate_vpc_cidr_block( AssociationId=association_id) except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: module.fail_json_aws( e, "Unable to disassociate {0}. You must detach or delete all gateways and resources that " "are associated with the CIDR block before you can disassociate it." .format(association_id)) if dhcp_id is not None: try: if update_dhcp_opts(connection, module, vpc_obj, dhcp_id): changed = True except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: module.fail_json_aws(e, "Failed to update DHCP options") if tags is not None or name is not None: try: if update_vpc_tags(connection, module, vpc_id, tags, name): changed = True except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: module.fail_json_aws(e, msg="Failed to update tags") current_dns_enabled = connection.describe_vpc_attribute( Attribute='enableDnsSupport', VpcId=vpc_id, aws_retry=True)['EnableDnsSupport']['Value'] current_dns_hostnames = connection.describe_vpc_attribute( Attribute='enableDnsHostnames', VpcId=vpc_id, aws_retry=True)['EnableDnsHostnames']['Value'] if current_dns_enabled != dns_support: changed = True if not module.check_mode: try: connection.modify_vpc_attribute( VpcId=vpc_id, EnableDnsSupport={'Value': dns_support}) except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: module.fail_json_aws( e, "Failed to update enabled dns support attribute") if current_dns_hostnames != dns_hostnames: changed = True if not module.check_mode: try: connection.modify_vpc_attribute( VpcId=vpc_id, EnableDnsHostnames={'Value': dns_hostnames}) except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: module.fail_json_aws( e, "Failed to update enabled dns hostnames attribute") # wait for associated cidrs to match if to_add or to_remove: try: connection.get_waiter('vpc_available').wait( VpcIds=[vpc_id], Filters=[{ 'Name': 'cidr-block-association.cidr-block', 'Values': expected_cidrs }]) except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: module.fail_json_aws(e, "Failed to wait for CIDRs to update") # try to wait for enableDnsSupport and enableDnsHostnames to match wait_for_vpc_attribute(connection, module, vpc_id, 'enableDnsSupport', dns_support) wait_for_vpc_attribute(connection, module, vpc_id, 'enableDnsHostnames', dns_hostnames) final_state = camel_dict_to_snake_dict( get_vpc(module, connection, vpc_id)) final_state['tags'] = boto3_tag_list_to_ansible_dict( final_state.get('tags', [])) final_state['id'] = final_state.pop('vpc_id') module.exit_json(changed=changed, vpc=final_state) elif state == 'absent': # Check if VPC exists vpc_id = vpc_exists(module, connection, name, cidr_block, multi) if vpc_id is not None: try: if not module.check_mode: connection.delete_vpc(VpcId=vpc_id) changed = True except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: module.fail_json_aws( e, msg= "Failed to delete VPC {0} You may want to use the ec2_vpc_subnet, ec2_vpc_igw, " "and/or ec2_vpc_route_table modules to ensure the other components are absent." .format(vpc_id)) module.exit_json(changed=changed, vpc={})
def main(): argument_spec = dict( name=dict(required=True), cidr_block=dict(type='list', required=True), tenancy=dict(choices=['default', 'dedicated'], default='default'), dns_support=dict(type='bool', default=True), dns_hostnames=dict(type='bool', default=True), dhcp_opts_id=dict(), tags=dict(type='dict', aliases=['resource_tags']), state=dict(choices=['present', 'absent'], default='present'), multi_ok=dict(type='bool', default=False), purge_cidrs=dict(type='bool', default=False), ) module = AnsibleAWSModule( argument_spec=argument_spec, supports_check_mode=True ) name = module.params.get('name') cidr_block = module.params.get('cidr_block') purge_cidrs = module.params.get('purge_cidrs') tenancy = module.params.get('tenancy') dns_support = module.params.get('dns_support') dns_hostnames = module.params.get('dns_hostnames') dhcp_id = module.params.get('dhcp_opts_id') tags = module.params.get('tags') state = module.params.get('state') multi = module.params.get('multi_ok') changed = False connection = module.client( 'ec2', retry_decorator=AWSRetry.jittered_backoff( retries=8, delay=3, catch_extra_error_codes=['InvalidVpcID.NotFound'] ) ) if dns_hostnames and not dns_support: module.fail_json(msg='In order to enable DNS Hostnames you must also enable DNS support') if state == 'present': # Check if VPC exists vpc_id = vpc_exists(module, connection, name, cidr_block, multi) if vpc_id is None: vpc_id = create_vpc(connection, module, cidr_block[0], tenancy) changed = True vpc_obj = get_vpc(module, connection, vpc_id) associated_cidrs = dict((cidr['CidrBlock'], cidr['AssociationId']) for cidr in vpc_obj.get('CidrBlockAssociationSet', []) if cidr['CidrBlockState']['State'] != 'disassociated') to_add = [cidr for cidr in cidr_block if cidr not in associated_cidrs] to_remove = [associated_cidrs[cidr] for cidr in associated_cidrs if cidr not in cidr_block] expected_cidrs = [cidr for cidr in associated_cidrs if associated_cidrs[cidr] not in to_remove] + to_add if len(cidr_block) > 1: for cidr in to_add: changed = True connection.associate_vpc_cidr_block(CidrBlock=cidr, VpcId=vpc_id) if purge_cidrs: for association_id in to_remove: changed = True try: connection.disassociate_vpc_cidr_block(AssociationId=association_id) except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: module.fail_json_aws(e, "Unable to disassociate {0}. You must detach or delete all gateways and resources that " "are associated with the CIDR block before you can disassociate it.".format(association_id)) if dhcp_id is not None: try: if update_dhcp_opts(connection, module, vpc_obj, dhcp_id): changed = True except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: module.fail_json_aws(e, "Failed to update DHCP options") if tags is not None or name is not None: try: if update_vpc_tags(connection, module, vpc_id, tags, name): changed = True except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: module.fail_json_aws(e, msg="Failed to update tags") current_dns_enabled = connection.describe_vpc_attribute(Attribute='enableDnsSupport', VpcId=vpc_id, aws_retry=True)['EnableDnsSupport']['Value'] current_dns_hostnames = connection.describe_vpc_attribute(Attribute='enableDnsHostnames', VpcId=vpc_id, aws_retry=True)['EnableDnsHostnames']['Value'] if current_dns_enabled != dns_support: changed = True if not module.check_mode: try: connection.modify_vpc_attribute(VpcId=vpc_id, EnableDnsSupport={'Value': dns_support}) except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: module.fail_json_aws(e, "Failed to update enabled dns support attribute") if current_dns_hostnames != dns_hostnames: changed = True if not module.check_mode: try: connection.modify_vpc_attribute(VpcId=vpc_id, EnableDnsHostnames={'Value': dns_hostnames}) except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: module.fail_json_aws(e, "Failed to update enabled dns hostnames attribute") # wait for associated cidrs to match if to_add or to_remove: try: connection.get_waiter('vpc_available').wait( VpcIds=[vpc_id], Filters=[{'Name': 'cidr-block-association.cidr-block', 'Values': expected_cidrs}] ) except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: module.fail_json_aws(e, "Failed to wait for CIDRs to update") # try to wait for enableDnsSupport and enableDnsHostnames to match wait_for_vpc_attribute(connection, module, vpc_id, 'enableDnsSupport', dns_support) wait_for_vpc_attribute(connection, module, vpc_id, 'enableDnsHostnames', dns_hostnames) final_state = camel_dict_to_snake_dict(get_vpc(module, connection, vpc_id)) final_state['tags'] = boto3_tag_list_to_ansible_dict(final_state.get('tags', [])) final_state['id'] = final_state.pop('vpc_id') module.exit_json(changed=changed, vpc=final_state) elif state == 'absent': # Check if VPC exists vpc_id = vpc_exists(module, connection, name, cidr_block, multi) if vpc_id is not None: try: if not module.check_mode: connection.delete_vpc(VpcId=vpc_id) changed = True except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: module.fail_json_aws(e, msg="Failed to delete VPC {0} You may want to use the ec2_vpc_subnet, ec2_vpc_igw, " "and/or ec2_vpc_route_table modules to ensure the other components are absent.".format(vpc_id)) module.exit_json(changed=changed, vpc={})
def create_or_update(module, template_options): ec2 = module.client('ec2', retry_decorator=AWSRetry.jittered_backoff()) template, template_versions = existing_templates(module) out = {} lt_data = params_to_launch_data( module, dict( (k, v) for k, v in module.params.items() if k in template_options)) if not (template or template_versions): # create a full new one try: resp = ec2.create_launch_template( LaunchTemplateName=module.params['template_name'], LaunchTemplateData=lt_data, ClientToken=uuid4().hex, aws_retry=True, ) except (ClientError, BotoCoreError) as e: module.fail_json_aws(e, msg="Couldn't create launch template") template, template_versions = existing_templates(module) out['changed'] = True elif template and template_versions: most_recent = sorted(template_versions, key=lambda x: x['VersionNumber'])[-1] if lt_data == most_recent['LaunchTemplateData']: out['changed'] = False return out try: resp = ec2.create_launch_template_version( LaunchTemplateId=template['LaunchTemplateId'], LaunchTemplateData=lt_data, SourceVersion=module.params.get('source_version'), ClientToken=uuid4().hex, aws_retry=True, ) if module.params.get('default_version') in (None, ''): # no need to do anything, leave the existing version as default pass elif module.params.get('default_version') == 'latest': set_default = ec2.modify_launch_template( LaunchTemplateId=template['LaunchTemplateId'], DefaultVersion=to_text( resp['LaunchTemplateVersion']['VersionNumber']), ClientToken=uuid4().hex, aws_retry=True, ) else: try: int(module.params.get('default_version')) except ValueError: module.fail_json( msg= 'default_version param was not a valid integer, got "{0}"' .format(module.params.get('default_version'))) set_default = ec2.modify_launch_template( LaunchTemplateId=template['LaunchTemplateId'], DefaultVersion=to_text( int(module.params.get('default_version'))), ClientToken=uuid4().hex, aws_retry=True, ) except (ClientError, BotoCoreError) as e: module.fail_json_aws( e, msg="Couldn't create subsequent launch template version") template, template_versions = existing_templates(module) out['changed'] = True return out