def test_is_boto3_error_message_single__pass__botocore(self): passed_exception = self._make_botocore_exception() returned_exception = is_boto3_error_message( 'is not authorized to perform', e=passed_exception) self.assertFalse(isinstance(passed_exception, returned_exception)) self.assertFalse( issubclass(returned_exception, botocore.exceptions.ClientError)) self.assertFalse( issubclass(returned_exception, botocore.exceptions.BotoCoreError)) self.assertTrue(issubclass(returned_exception, Exception)) self.assertEqual(returned_exception.__name__, "NeverEverRaisedException")
def create_invalidation(self, distribution_id, invalidation_batch): current_invalidation_response = self.get_invalidation(distribution_id, invalidation_batch['CallerReference']) try: response = self.client.create_invalidation(DistributionId=distribution_id, InvalidationBatch=invalidation_batch) response.pop('ResponseMetadata', None) if current_invalidation_response: return response, False else: return response, True except is_boto3_error_message('Your request contains a caller reference that was used for a previous invalidation ' 'batch for the same distribution.'): self.module.warn("InvalidationBatch target paths are not modifiable. " "To make a new invalidation please update caller_reference.") return current_invalidation_response, False except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: # pylint: disable=duplicate-except self.module.fail_json_aws(e, msg="Error creating CloudFront invalidations.")
def create_lifecycle_rule(client, module): name = module.params.get("name") wait = module.params.get("wait") changed = False old_lifecycle_rules = fetch_rules(client, module, name) new_rule = build_rule(client, module) (changed, lifecycle_configuration) = compare_and_update_configuration( client, module, old_lifecycle_rules, new_rule) # Write lifecycle to bucket try: client.put_bucket_lifecycle_configuration( aws_retry=True, Bucket=name, LifecycleConfiguration=lifecycle_configuration) except is_boto3_error_message( 'At least one action needs to be specified in a rule'): # Amazon interpretted this as not changing anything changed = False except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: # pylint: disable=duplicate-except module.fail_json_aws(e, lifecycle_configuration=lifecycle_configuration, name=name, old_lifecycle_rules=old_lifecycle_rules) _changed = changed _retries = 10 while wait and _changed and _retries: # We've seen examples where get_bucket_lifecycle_configuration returns # the updated rules, then the old rules, then the updated rules again, time.sleep(5) _retries -= 1 new_rules = fetch_rules(client, module, name) (_changed, lifecycle_configuration) = compare_and_update_configuration( client, module, new_rules, new_rule) new_rules = fetch_rules(client, module, name) module.exit_json(changed=changed, new_rule=new_rule, rules=new_rules, old_rules=old_lifecycle_rules, _retries=_retries, _config=lifecycle_configuration)
def test_is_boto3_error_message_single__raise__client(self): caught_exception = None thrown_exception = self._make_denied_exception() # Test that we don't catch BotoCoreError try: raise thrown_exception except is_boto3_error_message('is not authorized to perform') as e: caught_exception = e caught = 'Message' except botocore.exceptions.ClientError as e: # pylint: disable=duplicate-except caught_exception = e caught = 'ClientError' except botocore.exceptions.BotoCoreError as e: caught_exception = e caught = 'BotoCoreError' except Exception as e: caught_exception = e caught = 'Exception' self.assertEqual(caught_exception, thrown_exception) self.assertEqual(caught, 'Message')
def main(): argument_spec = dict( app_name=dict(aliases=['name'], type='str', required=False), description=dict(), state=dict(choices=['present', 'absent'], default='present'), terminate_by_force=dict(type='bool', default=False, required=False) ) module = AnsibleAWSModule(argument_spec=argument_spec, supports_check_mode=True) app_name = module.params['app_name'] description = module.params['description'] state = module.params['state'] terminate_by_force = module.params['terminate_by_force'] if app_name is None: module.fail_json(msg='Module parameter "app_name" is required') result = {} ebs = module.client('elasticbeanstalk') app = describe_app(ebs, app_name, module) if module.check_mode: check_app(ebs, app, module) module.fail_json(msg='ASSERTION FAILURE: check_app() should not return control.') if state == 'present': if app is None: try: create_app = ebs.create_application(**filter_empty(ApplicationName=app_name, Description=description)) except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: module.fail_json_aws(e, msg="Could not create application") app = describe_app(ebs, app_name, module) result = dict(changed=True, app=app) else: if app.get("Description", None) != description: try: if not description: ebs.update_application(ApplicationName=app_name) else: ebs.update_application(ApplicationName=app_name, Description=description) except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: module.fail_json_aws(e, msg="Could not update application") app = describe_app(ebs, app_name, module) result = dict(changed=True, app=app) else: result = dict(changed=False, app=app) else: if app is None: result = dict(changed=False, output='Application not found', app={}) else: try: if terminate_by_force: # Running environments will be terminated before deleting the application ebs.delete_application(ApplicationName=app_name, TerminateEnvByForce=terminate_by_force) else: ebs.delete_application(ApplicationName=app_name) changed = True except is_boto3_error_message('It is currently pending deletion'): changed = False except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: # pylint: disable=duplicate-except module.fail_json_aws(e, msg="Cannot terminate app") result = dict(changed=changed, app=app) module.exit_json(**result)
def main(): argument_spec = dict( state=dict(type='str', required=True, choices=['absent', 'create', 'delete', 'get', 'present'], aliases=['command']), zone=dict(type='str'), hosted_zone_id=dict(type='str'), record=dict(type='str', required=True), ttl=dict(type='int', default=3600), type=dict(type='str', required=True, choices=[ 'A', 'AAAA', 'CAA', 'CNAME', 'MX', 'NS', 'PTR', 'SOA', 'SPF', 'SRV', 'TXT' ]), alias=dict(type='bool'), alias_hosted_zone_id=dict(type='str'), alias_evaluate_target_health=dict(type='bool', default=False), value=dict(type='list', elements='str'), overwrite=dict(type='bool'), retry_interval=dict(type='int', default=500), private_zone=dict(type='bool', default=False), identifier=dict(type='str'), weight=dict(type='int'), region=dict(type='str'), health_check=dict(type='str'), failover=dict(type='str', choices=['PRIMARY', 'SECONDARY']), vpc_id=dict(type='str'), wait=dict(type='bool', default=False), wait_timeout=dict(type='int', default=300), ) module = AnsibleAWSModule( argument_spec=argument_spec, supports_check_mode=True, required_one_of=[['zone', 'hosted_zone_id']], # If alias is True then you must specify alias_hosted_zone as well required_together=[['alias', 'alias_hosted_zone_id']], # state=present, absent, create, delete THEN value is required required_if=( ('state', 'present', ['value']), ('state', 'create', ['value']), ('state', 'absent', ['value']), ('state', 'delete', ['value']), ), # failover, region and weight are mutually exclusive mutually_exclusive=[ ('failover', 'region', 'weight'), ('alias', 'ttl'), ], # failover, region and weight require identifier required_by=dict( failover=('identifier', ), region=('identifier', ), weight=('identifier', ), ), ) if module.params['state'] in ('present', 'create'): command_in = 'create' elif module.params['state'] in ('absent', 'delete'): command_in = 'delete' elif module.params['state'] == 'get': command_in = 'get' zone_in = (module.params.get('zone') or '').lower() hosted_zone_id_in = module.params.get('hosted_zone_id') ttl_in = module.params.get('ttl') record_in = module.params.get('record').lower() type_in = module.params.get('type') value_in = module.params.get('value') or [] alias_in = module.params.get('alias') alias_hosted_zone_id_in = module.params.get('alias_hosted_zone_id') alias_evaluate_target_health_in = module.params.get( 'alias_evaluate_target_health') retry_interval_in = module.params.get('retry_interval') if module.params['vpc_id'] is not None: private_zone_in = True else: private_zone_in = module.params.get('private_zone') identifier_in = module.params.get('identifier') weight_in = module.params.get('weight') region_in = module.params.get('region') health_check_in = module.params.get('health_check') failover_in = module.params.get('failover') vpc_id_in = module.params.get('vpc_id') wait_in = module.params.get('wait') wait_timeout_in = module.params.get('wait_timeout') if zone_in[-1:] != '.': zone_in += "." if record_in[-1:] != '.': record_in += "." if command_in == 'create' or command_in == 'delete': if alias_in and len(value_in) != 1: module.fail_json( msg= "parameter 'value' must contain a single dns name for alias records" ) if (weight_in is None and region_in is None and failover_in is None) and identifier_in is not None: module.fail_json( msg= "You have specified identifier which makes sense only if you specify one of: weight, region or failover." ) retry_decorator = AWSRetry.jittered_backoff( retries=MAX_AWS_RETRIES, delay=retry_interval_in, catch_extra_error_codes=['PriorRequestNotComplete'], max_delay=max(60, retry_interval_in), ) # connect to the route53 endpoint try: route53 = module.client('route53', retry_decorator=retry_decorator) except botocore.exceptions.HTTPClientError as e: module.fail_json_aws(e, msg='Failed to connect to AWS') # Find the named zone ID zone_id = hosted_zone_id_in or get_zone_id_by_name( route53, module, zone_in, private_zone_in, vpc_id_in) # Verify that the requested zone is already defined in Route53 if zone_id is None: errmsg = "Zone %s does not exist in Route53" % (zone_in or hosted_zone_id_in) module.fail_json(msg=errmsg) aws_record = get_record(route53, zone_id, record_in, type_in, identifier_in) resource_record_set = scrub_none_parameters({ 'Name': record_in, 'Type': type_in, 'Weight': weight_in, 'Region': region_in, 'Failover': failover_in, 'TTL': ttl_in, 'ResourceRecords': [dict(Value=value) for value in value_in], 'HealthCheckId': health_check_in, }) if alias_in: resource_record_set['AliasTarget'] = dict( HostedZoneId=alias_hosted_zone_id_in, DNSName=value_in[0], EvaluateTargetHealth=alias_evaluate_target_health_in) if 'ResourceRecords' in resource_record_set: del resource_record_set['ResourceRecords'] if 'TTL' in resource_record_set: del resource_record_set['TTL'] # On CAA records order doesn't matter if type_in == 'CAA': resource_record_set['ResourceRecords'] = sorted( resource_record_set['ResourceRecords'], key=itemgetter('Value')) if aws_record: aws_record['ResourceRecords'] = sorted( aws_record['ResourceRecords'], key=itemgetter('Value')) if command_in == 'create' and aws_record == resource_record_set: rr_sets = [camel_dict_to_snake_dict(resource_record_set)] module.exit_json(changed=False, resource_records_sets=rr_sets) if command_in == 'get': if type_in == 'NS': ns = aws_record.get('values', []) else: # Retrieve name servers associated to the zone. ns = get_hosted_zone_nameservers(route53, zone_id) formatted_aws = format_record(aws_record, zone_in, zone_id) rr_sets = [camel_dict_to_snake_dict(aws_record)] module.exit_json(changed=False, set=formatted_aws, nameservers=ns, resource_record_sets=rr_sets) if command_in == 'delete' and not aws_record: module.exit_json(changed=False) if command_in == 'create' or command_in == 'delete': if command_in == 'create' and aws_record: if not module.params['overwrite']: module.fail_json( msg= "Record already exists with different value. Set 'overwrite' to replace it" ) command = 'UPSERT' else: command = command_in.upper() if not module.check_mode: try: change_resource_record_sets = route53.change_resource_record_sets( aws_retry=True, HostedZoneId=zone_id, ChangeBatch=dict(Changes=[ dict(Action=command, ResourceRecordSet=resource_record_set) ])) if wait_in: waiter = get_waiter(route53, 'resource_record_sets_changed') waiter.wait(Id=change_resource_record_sets['ChangeInfo']['Id'], WaiterConfig=dict( Delay=WAIT_RETRY, MaxAttempts=wait_timeout_in // WAIT_RETRY, )) except is_boto3_error_message('but it already exists'): module.exit_json(changed=False) except botocore.exceptions.WaiterError as e: module.fail_json_aws( e, msg='Timeout waiting for resource records changes to be applied' ) except (botocore.exceptions.BotoCoreError, botocore.exceptions.ClientError) as e: # pylint: disable=duplicate-except module.fail_json_aws(e, msg='Failed to update records') except Exception as e: module.fail_json(msg='Unhandled exception. (%s)' % to_native(e)) rr_sets = [camel_dict_to_snake_dict(resource_record_set)] formatted_aws = format_record(aws_record, zone_in, zone_id) formatted_record = format_record(resource_record_set, zone_in, zone_id) module.exit_json( changed=True, diff=dict( before=formatted_aws, after=formatted_record if command != 'delete' else {}, resource_record_sets=rr_sets, ), )
def get_secret_value(self, term, client, version_stage=None, version_id=None, on_missing=None, on_denied=None, on_deleted=None, nested=False): params = {} params['SecretId'] = term if version_id: params['VersionId'] = version_id if version_stage: params['VersionStage'] = version_stage if nested: if len(term.split('.')) < 2: raise AnsibleError( "Nested query must use the following syntax: `aws_secret_name.<key_name>.<key_name>" ) secret_name = term.split('.')[0] params['SecretId'] = secret_name try: response = client.get_secret_value(**params) if 'SecretBinary' in response: return response['SecretBinary'] if 'SecretString' in response: if nested: secrets = [] query = term.split('.')[1:] secret_string = json.loads(response['SecretString']) ret_val = secret_string for key in query: if key in ret_val: ret_val = ret_val[key] else: raise AnsibleError( "Successfully retrieved secret but there exists no key {0} in the secret" .format(key)) return str(ret_val) else: return response['SecretString'] except is_boto3_error_message('marked for deletion'): if on_deleted == 'error': raise AnsibleError( "Failed to find secret %s (marked for deletion)" % term) elif on_deleted == 'warn': self._display.warning( 'Skipping, did not find secret (marked for deletion) %s' % term) except is_boto3_error_code('ResourceNotFoundException'): # pylint: disable=duplicate-except if on_missing == 'error': raise AnsibleError( "Failed to find secret %s (ResourceNotFound)" % term) elif on_missing == 'warn': self._display.warning('Skipping, did not find secret %s' % term) except is_boto3_error_code('AccessDeniedException'): # pylint: disable=duplicate-except if on_denied == 'error': raise AnsibleError( "Failed to access secret %s (AccessDenied)" % term) elif on_denied == 'warn': self._display.warning('Skipping, access denied for secret %s' % term) except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e: # pylint: disable=duplicate-except raise AnsibleError("Failed to retrieve secret: %s" % to_native(e)) return None