Merge pull request #9 from spulec/master

Merge upstream
This commit is contained in:
Bert Blommers 2019-10-22 07:36:30 +01:00 committed by GitHub
commit 21d2fac468
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 1193 additions and 81 deletions

View File

@ -3163,7 +3163,7 @@
- [ ] describe_events
## iam
55% implemented
57% implemented
- [ ] add_client_id_to_open_id_connect_provider
- [X] add_role_to_instance_profile
- [X] add_user_to_group
@ -3176,7 +3176,7 @@
- [X] create_group
- [X] create_instance_profile
- [X] create_login_profile
- [ ] create_open_id_connect_provider
- [X] create_open_id_connect_provider
- [X] create_policy
- [X] create_policy_version
- [X] create_role
@ -3193,8 +3193,8 @@
- [ ] delete_group_policy
- [ ] delete_instance_profile
- [X] delete_login_profile
- [ ] delete_open_id_connect_provider
- [ ] delete_policy
- [X] delete_open_id_connect_provider
- [X] delete_policy
- [X] delete_policy_version
- [X] delete_role
- [ ] delete_role_permissions_boundary
@ -3227,7 +3227,7 @@
- [X] get_group_policy
- [X] get_instance_profile
- [X] get_login_profile
- [ ] get_open_id_connect_provider
- [X] get_open_id_connect_provider
- [ ] get_organizations_access_report
- [X] get_policy
- [X] get_policy_version
@ -3253,7 +3253,7 @@
- [ ] list_instance_profiles
- [ ] list_instance_profiles_for_role
- [X] list_mfa_devices
- [ ] list_open_id_connect_providers
- [X] list_open_id_connect_providers
- [X] list_policies
- [ ] list_policies_granting_service_access
- [X] list_policy_versions
@ -5902,7 +5902,7 @@
- [x] untag_resource
## sqs
65% implemented
75% implemented
- [X] add_permission
- [X] change_message_visibility
- [ ] change_message_visibility_batch
@ -5913,13 +5913,13 @@
- [ ] get_queue_attributes
- [ ] get_queue_url
- [X] list_dead_letter_source_queues
- [ ] list_queue_tags
- [x] list_queue_tags
- [X] list_queues
- [X] purge_queue
- [ ] receive_message
- [X] remove_permission
- [X] send_message
- [ ] send_message_batch
- [x] send_message_batch
- [X] set_queue_attributes
- [X] tag_queue
- [X] untag_queue

View File

@ -457,6 +457,9 @@ class DynamoHandler(BaseResponse):
range_comparison = None
range_values = []
if '=' not in hash_key_expression:
return self.error('com.amazonaws.dynamodb.v20111205#ValidationException',
'Query key condition not supported')
hash_key_value_alias = hash_key_expression.split("=")[1].strip()
# Temporary fix until we get proper KeyConditionExpression function
hash_key = value_alias_map.get(hash_key_value_alias, {'S': hash_key_value_alias})

View File

@ -50,7 +50,7 @@ EC2_PREFIX_TO_RESOURCE = dict((v, k) for (k, v) in EC2_RESOURCE_TO_PREFIX.items(
def random_resource_id(size=8):
chars = list(range(10)) + ['a', 'b', 'c', 'd', 'e', 'f']
resource_id = ''.join(six.text_type(random.choice(chars)) for x in range(size))
resource_id = ''.join(six.text_type(random.choice(chars)) for _ in range(size))
return resource_id
@ -460,8 +460,8 @@ def generic_filter(filters, objects):
def simple_aws_filter_to_re(filter_string):
tmp_filter = filter_string.replace('\?', '[?]')
tmp_filter = tmp_filter.replace('\*', '[*]')
tmp_filter = filter_string.replace(r'\?', '[?]')
tmp_filter = tmp_filter.replace(r'\*', '[*]')
tmp_filter = fnmatch.translate(tmp_filter)
return tmp_filter
@ -491,7 +491,7 @@ def get_prefix(resource_id):
'network-interface-attachment']
if resource_id_prefix not in EC2_RESOURCE_TO_PREFIX.values():
uuid4hex = re.compile(
'[0-9a-f]{12}4[0-9a-f]{3}[89ab][0-9a-f]{15}\Z', re.I)
r'[0-9a-f]{12}4[0-9a-f]{3}[89ab][0-9a-f]{15}\Z', re.I)
if uuid4hex.match(resource_id) is not None:
resource_id_prefix = EC2_RESOURCE_TO_PREFIX['reserved-instance']
else:
@ -510,7 +510,7 @@ def is_valid_resource_id(resource_id):
def is_valid_cidr(cird):
cidr_pattern = '^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\/(\d|[1-2]\d|3[0-2]))$'
cidr_pattern = r'^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\/(\d|[1-2]\d|3[0-2]))$'
cidr_pattern_re = re.compile(cidr_pattern)
return cidr_pattern_re.match(cird) is not None

View File

@ -93,3 +93,27 @@ class TooManyTags(RESTError):
super(TooManyTags, self).__init__(
'ValidationError', "1 validation error detected: Value '{}' at '{}' failed to satisfy "
"constraint: Member must have length less than or equal to 50.".format(tags, param))
class EntityAlreadyExists(RESTError):
code = 409
def __init__(self):
super(EntityAlreadyExists, self).__init__(
'EntityAlreadyExists', "Unknown")
class ValidationError(RESTError):
code = 400
def __init__(self, message):
super(ValidationError, self).__init__(
'ValidationError', message)
class InvalidInput(RESTError):
code = 400
def __init__(self, message):
super(InvalidInput, self).__init__(
'InvalidInput', message)

View File

@ -7,6 +7,7 @@ import re
from cryptography import x509
from cryptography.hazmat.backends import default_backend
from six.moves.urllib.parse import urlparse
from moto.core.exceptions import RESTError
from moto.core import BaseBackend, BaseModel
@ -14,8 +15,9 @@ from moto.core.utils import iso_8601_datetime_without_milliseconds, iso_8601_dat
from moto.iam.policy_validation import IAMPolicyDocumentValidator
from .aws_managed_policies import aws_managed_policies_data
from .exceptions import IAMNotFoundException, IAMConflictException, IAMReportNotPresentException, IAMLimitExceededException, \
MalformedCertificate, DuplicateTags, TagKeyTooBig, InvalidTagCharacters, TooManyTags, TagValueTooBig
from .exceptions import (IAMNotFoundException, IAMConflictException, IAMReportNotPresentException, IAMLimitExceededException,
MalformedCertificate, DuplicateTags, TagKeyTooBig, InvalidTagCharacters, TooManyTags, TagValueTooBig,
EntityAlreadyExists, ValidationError, InvalidInput)
from .utils import random_access_key, random_alphanumeric, random_resource_id, random_policy_id
ACCOUNT_ID = 123456789012
@ -93,6 +95,82 @@ class SAMLProvider(BaseModel):
return "arn:aws:iam::{0}:saml-provider/{1}".format(ACCOUNT_ID, self.name)
class OpenIDConnectProvider(BaseModel):
def __init__(self, url, thumbprint_list, client_id_list=None):
self._errors = []
self._validate(url, thumbprint_list, client_id_list)
parsed_url = urlparse(url)
self.url = parsed_url.netloc + parsed_url.path
self.thumbprint_list = thumbprint_list
self.client_id_list = client_id_list
self.create_date = datetime.utcnow()
@property
def arn(self):
return 'arn:aws:iam::{0}:oidc-provider/{1}'.format(ACCOUNT_ID, self.url)
@property
def created_iso_8601(self):
return iso_8601_datetime_without_milliseconds(self.create_date)
def _validate(self, url, thumbprint_list, client_id_list):
if any(len(client_id) > 255 for client_id in client_id_list):
self._errors.append(self._format_error(
key='clientIDList',
value=client_id_list,
constraint='Member must satisfy constraint: '
'[Member must have length less than or equal to 255, '
'Member must have length greater than or equal to 1]',
))
if any(len(thumbprint) > 40 for thumbprint in thumbprint_list):
self._errors.append(self._format_error(
key='thumbprintList',
value=thumbprint_list,
constraint='Member must satisfy constraint: '
'[Member must have length less than or equal to 40, '
'Member must have length greater than or equal to 40]',
))
if len(url) > 255:
self._errors.append(self._format_error(
key='url',
value=url,
constraint='Member must have length less than or equal to 255',
))
self._raise_errors()
parsed_url = urlparse(url)
if not parsed_url.scheme or not parsed_url.netloc:
raise ValidationError('Invalid Open ID Connect Provider URL')
if len(thumbprint_list) > 5:
raise InvalidInput('Thumbprint list must contain fewer than 5 entries.')
if len(client_id_list) > 100:
raise IAMLimitExceededException('Cannot exceed quota for ClientIdsPerOpenIdConnectProvider: 100')
def _format_error(self, key, value, constraint):
return 'Value "{value}" at "{key}" failed to satisfy constraint: {constraint}'.format(
constraint=constraint,
key=key,
value=value,
)
def _raise_errors(self):
if self._errors:
count = len(self._errors)
plural = "s" if len(self._errors) > 1 else ""
errors = "; ".join(self._errors)
self._errors = [] # reset collected errors
raise ValidationError('{count} validation error{plural} detected: {errors}'.format(
count=count, plural=plural, errors=errors,
))
class PolicyVersion(object):
def __init__(self,
@ -515,6 +593,7 @@ class IAMBackend(BaseBackend):
self.managed_policies = self._init_managed_policies()
self.account_aliases = []
self.saml_providers = {}
self.open_id_providers = {}
self.policy_arn_regex = re.compile(
r'^arn:aws:iam::[0-9]*:policy/.*$')
super(IAMBackend, self).__init__()
@ -1099,6 +1178,9 @@ class IAMBackend(BaseBackend):
user = self.get_user(user_name)
user.delete_policy(policy_name)
def delete_policy(self, policy_arn):
del self.managed_policies[policy_arn]
def create_access_key(self, user_name=None):
user = self.get_user(user_name)
key = user.create_access_key()
@ -1261,5 +1343,28 @@ class IAMBackend(BaseBackend):
return user
return None
def create_open_id_connect_provider(self, url, thumbprint_list, client_id_list):
open_id_provider = OpenIDConnectProvider(url, thumbprint_list, client_id_list)
if open_id_provider.arn in self.open_id_providers:
raise EntityAlreadyExists
self.open_id_providers[open_id_provider.arn] = open_id_provider
return open_id_provider
def delete_open_id_connect_provider(self, arn):
self.open_id_providers.pop(arn, None)
def get_open_id_connect_provider(self, arn):
open_id_provider = self.open_id_providers.get(arn)
if not open_id_provider:
raise IAMNotFoundException('OpenIDConnect Provider not found for arn {}'.format(arn))
return open_id_provider
def list_open_id_connect_providers(self):
return list(self.open_id_providers.keys())
iam_backend = IAMBackend()

View File

@ -604,6 +604,12 @@ class IamResponse(BaseResponse):
template = self.response_template(GENERIC_EMPTY_TEMPLATE)
return template.render(name='DeleteUser')
def delete_policy(self):
policy_arn = self._get_param('PolicyArn')
iam_backend.delete_policy(policy_arn)
template = self.response_template(GENERIC_EMPTY_TEMPLATE)
return template.render(name='DeletePolicy')
def delete_login_profile(self):
user_name = self._get_param('UserName')
iam_backend.delete_login_profile(user_name)
@ -749,6 +755,38 @@ class IamResponse(BaseResponse):
template = self.response_template(UNTAG_ROLE_TEMPLATE)
return template.render()
def create_open_id_connect_provider(self):
open_id_provider_url = self._get_param('Url')
thumbprint_list = self._get_multi_param('ThumbprintList.member')
client_id_list = self._get_multi_param('ClientIDList.member')
open_id_provider = iam_backend.create_open_id_connect_provider(open_id_provider_url, thumbprint_list, client_id_list)
template = self.response_template(CREATE_OPEN_ID_CONNECT_PROVIDER_TEMPLATE)
return template.render(open_id_provider=open_id_provider)
def delete_open_id_connect_provider(self):
open_id_provider_arn = self._get_param('OpenIDConnectProviderArn')
iam_backend.delete_open_id_connect_provider(open_id_provider_arn)
template = self.response_template(DELETE_OPEN_ID_CONNECT_PROVIDER_TEMPLATE)
return template.render()
def get_open_id_connect_provider(self):
open_id_provider_arn = self._get_param('OpenIDConnectProviderArn')
open_id_provider = iam_backend.get_open_id_connect_provider(open_id_provider_arn)
template = self.response_template(GET_OPEN_ID_CONNECT_PROVIDER_TEMPLATE)
return template.render(open_id_provider=open_id_provider)
def list_open_id_connect_providers(self):
open_id_provider_arns = iam_backend.list_open_id_connect_providers()
template = self.response_template(LIST_OPEN_ID_CONNECT_PROVIDERS_TEMPLATE)
return template.render(open_id_provider_arns=open_id_provider_arns)
LIST_ENTITIES_FOR_POLICY_TEMPLATE = """<ListEntitiesForPolicyResponse>
<ListEntitiesForPolicyResult>
@ -1968,3 +2006,57 @@ UNTAG_ROLE_TEMPLATE = """<UntagRoleResponse xmlns="https://iam.amazonaws.com/doc
<RequestId>EXAMPLE8-90ab-cdef-fedc-ba987EXAMPLE</RequestId>
</ResponseMetadata>
</UntagRoleResponse>"""
CREATE_OPEN_ID_CONNECT_PROVIDER_TEMPLATE = """<CreateOpenIDConnectProviderResponse xmlns="https://iam.amazonaws.com/doc/2010-05-08/">
<CreateOpenIDConnectProviderResult>
<OpenIDConnectProviderArn>{{ open_id_provider.arn }}</OpenIDConnectProviderArn>
</CreateOpenIDConnectProviderResult>
<ResponseMetadata>
<RequestId>f248366a-4f64-11e4-aefa-bfd6aEXAMPLE</RequestId>
</ResponseMetadata>
</CreateOpenIDConnectProviderResponse>"""
DELETE_OPEN_ID_CONNECT_PROVIDER_TEMPLATE = """<DeleteOpenIDConnectProviderResponse xmlns="https://iam.amazonaws.com/doc/2010-05-08/">
<ResponseMetadata>
<RequestId>b5e49e29-4f64-11e4-aefa-bfd6aEXAMPLE</RequestId>
</ResponseMetadata>
</DeleteOpenIDConnectProviderResponse>"""
GET_OPEN_ID_CONNECT_PROVIDER_TEMPLATE = """<GetOpenIDConnectProviderResponse xmlns="https://iam.amazonaws.com/doc/2010-05-08/">
<GetOpenIDConnectProviderResult>
<ThumbprintList>
{% for thumbprint in open_id_provider.thumbprint_list %}
<member>{{ thumbprint }}</member>
{% endfor %}
</ThumbprintList>
<CreateDate>{{ open_id_provider.created_iso_8601 }}</CreateDate>
<ClientIDList>
{% for client_id in open_id_provider.client_id_list %}
<member>{{ client_id }}</member>
{% endfor %}
</ClientIDList>
<Url>{{ open_id_provider.url }}</Url>
</GetOpenIDConnectProviderResult>
<ResponseMetadata>
<RequestId>2c91531b-4f65-11e4-aefa-bfd6aEXAMPLE</RequestId>
</ResponseMetadata>
</GetOpenIDConnectProviderResponse>"""
LIST_OPEN_ID_CONNECT_PROVIDERS_TEMPLATE = """<ListOpenIDConnectProvidersResponse xmlns="https://iam.amazonaws.com/doc/2010-05-08/">
<ListOpenIDConnectProvidersResult>
<OpenIDConnectProviderList>
{% for open_id_provider_arn in open_id_provider_arns %}
<member>
<Arn>{{ open_id_provider_arn }}</Arn>
</member>
{% endfor %}
</OpenIDConnectProviderList>
</ListOpenIDConnectProvidersResult>
<ResponseMetadata>
<RequestId>de2c0228-4f63-11e4-aefa-bfd6aEXAMPLE</RequestId>
</ResponseMetadata>
</ListOpenIDConnectProvidersResponse>"""

View File

@ -56,6 +56,8 @@ class Database(BaseModel):
else:
self.kms_key_id = kwargs.get("kms_key_id")
self.storage_type = kwargs.get("storage_type")
if self.storage_type is None:
self.storage_type = Database.default_storage_type(iops=self.iops)
self.master_username = kwargs.get('master_username')
self.master_user_password = kwargs.get('master_user_password')
self.auto_minor_version_upgrade = kwargs.get(
@ -63,6 +65,8 @@ class Database(BaseModel):
if self.auto_minor_version_upgrade is None:
self.auto_minor_version_upgrade = True
self.allocated_storage = kwargs.get('allocated_storage')
if self.allocated_storage is None:
self.allocated_storage = Database.default_allocated_storage(engine=self.engine, storage_type=self.storage_type)
self.db_instance_identifier = kwargs.get('db_instance_identifier')
self.source_db_identifier = kwargs.get("source_db_identifier")
self.db_instance_class = kwargs.get('db_instance_class')
@ -292,6 +296,78 @@ class Database(BaseModel):
'sqlserver-web': 1433,
}[engine]
@staticmethod
def default_storage_type(iops):
if iops is None:
return 'gp2'
else:
return 'io1'
@staticmethod
def default_allocated_storage(engine, storage_type):
return {
'aurora': {
'gp2': 0,
'io1': 0,
'standard': 0,
},
'mysql': {
'gp2': 20,
'io1': 100,
'standard': 5,
},
'mariadb': {
'gp2': 20,
'io1': 100,
'standard': 5,
},
'postgres': {
'gp2': 20,
'io1': 100,
'standard': 5,
},
'oracle-ee': {
'gp2': 20,
'io1': 100,
'standard': 10,
},
'oracle-se2': {
'gp2': 20,
'io1': 100,
'standard': 10,
},
'oracle-se1': {
'gp2': 20,
'io1': 100,
'standard': 10,
},
'oracle-se': {
'gp2': 20,
'io1': 100,
'standard': 10,
},
'sqlserver-ee': {
'gp2': 200,
'io1': 200,
'standard': 200,
},
'sqlserver-ex': {
'gp2': 20,
'io1': 100,
'standard': 20,
},
'sqlserver-se': {
'gp2': 200,
'io1': 200,
'standard': 200,
},
'sqlserver-web': {
'gp2': 20,
'io1': 100,
'standard': 20,
},
}[engine][storage_type]
@classmethod
def create_from_cloudformation_json(cls, resource_name, cloudformation_json, region_name):
properties = cloudformation_json['Properties']

View File

@ -42,7 +42,7 @@ class RDS2Response(BaseResponse):
"region": self.region,
"security_groups": self._get_multi_param('DBSecurityGroups.DBSecurityGroupName'),
"storage_encrypted": self._get_param("StorageEncrypted"),
"storage_type": self._get_param("StorageType", 'standard'),
"storage_type": self._get_param("StorageType", None),
"vpc_security_group_ids": self._get_multi_param("VpcSecurityGroupIds.VpcSecurityGroupId"),
"tags": list(),
}

View File

@ -1107,7 +1107,7 @@ class S3Backend(BaseBackend):
key = key_version
break
if part_number and key.multipart:
if part_number and key and key.multipart:
key = key.multipart.parts[part_number]
if isinstance(key, FakeKey):
@ -1115,8 +1115,8 @@ class S3Backend(BaseBackend):
else:
return None
def set_key_tagging(self, bucket_name, key_name, tagging):
key = self.get_key(bucket_name, key_name)
def set_key_tagging(self, bucket_name, key_name, tagging, version_id=None):
key = self.get_key(bucket_name, key_name, version_id)
if key is None:
raise MissingKey(key_name)
key.set_tagging(tagging)

View File

@ -905,8 +905,12 @@ class ResponseObject(_TemplateEnvironmentMixin, ActionAuthenticatorMixin):
return 200, response_headers, ""
if 'tagging' in query:
if 'versionId' in query:
version_id = query['versionId'][0]
else:
version_id = None
tagging = self._tagging_from_xml(body)
self.backend.set_key_tagging(bucket_name, key_name, tagging)
self.backend.set_key_tagging(bucket_name, key_name, tagging, version_id)
return 200, response_headers, ""
if 'x-amz-copy-source' in request.headers:

View File

@ -7,11 +7,32 @@ class SecretsManagerClientError(JsonRESTError):
class ResourceNotFoundException(SecretsManagerClientError):
def __init__(self):
def __init__(self, message):
self.code = 404
super(ResourceNotFoundException, self).__init__(
"ResourceNotFoundException",
"Secrets Manager can't find the specified secret"
message,
)
# Using specialised exception due to the use of a non-ASCII character
class SecretNotFoundException(SecretsManagerClientError):
def __init__(self):
self.code = 404
super(SecretNotFoundException, self).__init__(
"ResourceNotFoundException",
message=u"Secrets Manager can\u2019t find the specified secret."
)
# Using specialised exception due to the use of a non-ASCII character
class SecretHasNoValueException(SecretsManagerClientError):
def __init__(self, version_stage):
self.code = 404
super(SecretHasNoValueException, self).__init__(
"ResourceNotFoundException",
message=u"Secrets Manager can\u2019t find the specified secret "
u"value for staging label: {}".format(version_stage)
)

View File

@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
import time
@ -9,7 +10,8 @@ import boto3
from moto.core import BaseBackend, BaseModel
from .exceptions import (
ResourceNotFoundException,
SecretNotFoundException,
SecretHasNoValueException,
InvalidParameterException,
ResourceExistsException,
InvalidRequestException,
@ -46,7 +48,7 @@ class SecretsManagerBackend(BaseBackend):
def get_secret_value(self, secret_id, version_id, version_stage):
if not self._is_valid_identifier(secret_id):
raise ResourceNotFoundException()
raise SecretNotFoundException()
if not version_id and version_stage:
# set version_id to match version_stage
@ -56,7 +58,7 @@ class SecretsManagerBackend(BaseBackend):
version_id = ver_id
break
if not version_id:
raise ResourceNotFoundException()
raise SecretNotFoundException()
# TODO check this part
if 'deleted_date' in self.secrets[secret_id]:
@ -84,6 +86,9 @@ class SecretsManagerBackend(BaseBackend):
if 'secret_binary' in secret_version:
response_data["SecretBinary"] = secret_version['secret_binary']
if 'secret_string' not in secret_version and 'secret_binary' not in secret_version:
raise SecretHasNoValueException(version_stage or u"AWSCURRENT")
response = json.dumps(response_data)
return response
@ -169,7 +174,7 @@ class SecretsManagerBackend(BaseBackend):
def describe_secret(self, secret_id):
if not self._is_valid_identifier(secret_id):
raise ResourceNotFoundException
raise SecretNotFoundException()
secret = self.secrets[secret_id]
@ -198,7 +203,7 @@ class SecretsManagerBackend(BaseBackend):
rotation_days = 'AutomaticallyAfterDays'
if not self._is_valid_identifier(secret_id):
raise ResourceNotFoundException
raise SecretNotFoundException()
if 'deleted_date' in self.secrets[secret_id]:
raise InvalidRequestException(
@ -340,7 +345,7 @@ class SecretsManagerBackend(BaseBackend):
def delete_secret(self, secret_id, recovery_window_in_days, force_delete_without_recovery):
if not self._is_valid_identifier(secret_id):
raise ResourceNotFoundException
raise SecretNotFoundException()
if 'deleted_date' in self.secrets[secret_id]:
raise InvalidRequestException(
@ -370,7 +375,7 @@ class SecretsManagerBackend(BaseBackend):
secret = self.secrets.get(secret_id, None)
if not secret:
raise ResourceNotFoundException
raise SecretNotFoundException()
arn = secret_arn(self.region, secret['secret_id'])
name = secret['name']
@ -380,7 +385,7 @@ class SecretsManagerBackend(BaseBackend):
def restore_secret(self, secret_id):
if not self._is_valid_identifier(secret_id):
raise ResourceNotFoundException
raise SecretNotFoundException()
self.secrets[secret_id].pop('deleted_date', None)

View File

@ -343,6 +343,14 @@ class SNSBackend(BaseBackend):
return old_subscription
topic = self.get_topic(topic_arn)
subscription = Subscription(topic, endpoint, protocol)
attributes = {
'PendingConfirmation': 'false',
'Endpoint': endpoint,
'TopicArn': topic_arn,
'Protocol': protocol,
'SubscriptionArn': subscription.arn
}
subscription.attributes = attributes
self.subscriptions[subscription.arn] = subscription
return subscription

View File

@ -19,9 +19,12 @@ class MessageAttributesInvalid(Exception):
self.description = description
class QueueDoesNotExist(Exception):
status_code = 404
description = "The specified queue does not exist for this wsdl version."
class QueueDoesNotExist(RESTError):
code = 404
def __init__(self):
super(QueueDoesNotExist, self).__init__(
"QueueDoesNotExist", "The specified queue does not exist for this wsdl version.")
class QueueAlreadyExists(RESTError):

View File

@ -654,12 +654,21 @@ class SQSBackend(BaseBackend):
def tag_queue(self, queue_name, tags):
queue = self.get_queue(queue_name)
if not len(tags):
raise RESTError('MissingParameter',
'The request must contain the parameter Tags.')
if len(tags) > 50:
raise RESTError('InvalidParameterValue',
'Too many tags added for queue {}.'.format(queue_name))
queue.tags.update(tags)
def untag_queue(self, queue_name, tag_keys):
queue = self.get_queue(queue_name)
if len(tag_keys) == 0:
if not len(tag_keys):
raise RESTError('InvalidParameterValue', 'Tag keys must be between 1 and 128 characters in length.')
for key in tag_keys:

View File

@ -10,7 +10,6 @@ from .models import sqs_backends
from .exceptions import (
MessageAttributesInvalid,
MessageNotInflight,
QueueDoesNotExist,
ReceiptHandleIsInvalid,
)
@ -90,11 +89,7 @@ class SQSResponse(BaseResponse):
request_url = urlparse(self.uri)
queue_name = self._get_param("QueueName")
try:
queue = self.sqs_backend.get_queue(queue_name)
except QueueDoesNotExist as e:
return self._error('AWS.SimpleQueueService.NonExistentQueue',
e.description)
queue = self.sqs_backend.get_queue(queue_name)
if queue:
template = self.response_template(GET_QUEUE_URL_RESPONSE)
@ -175,11 +170,8 @@ class SQSResponse(BaseResponse):
def get_queue_attributes(self):
queue_name = self._get_queue_name()
try:
queue = self.sqs_backend.get_queue(queue_name)
except QueueDoesNotExist as e:
return self._error('AWS.SimpleQueueService.NonExistentQueue',
e.description)
queue = self.sqs_backend.get_queue(queue_name)
template = self.response_template(GET_QUEUE_ATTRIBUTES_RESPONSE)
return template.render(queue=queue)
@ -242,25 +234,55 @@ class SQSResponse(BaseResponse):
queue_name = self._get_queue_name()
messages = []
for index in range(1, 11):
# Loop through looking for messages
message_key = 'SendMessageBatchRequestEntry.{0}.MessageBody'.format(
index)
message_body = self.querystring.get(message_key)
if not message_body:
# Found all messages
break
self.sqs_backend.get_queue(queue_name)
message_user_id_key = 'SendMessageBatchRequestEntry.{0}.Id'.format(
index)
message_user_id = self.querystring.get(message_user_id_key)[0]
if self.querystring.get('Entries'):
return self._error('AWS.SimpleQueueService.EmptyBatchRequest',
'There should be at least one SendMessageBatchRequestEntry in the request.')
entries = {}
for key, value in self.querystring.items():
match = re.match(r'^SendMessageBatchRequestEntry\.(\d+)\.Id', key)
if match:
entries[match.group(1)] = {
'Id': value[0],
'MessageBody': self.querystring.get(
'SendMessageBatchRequestEntry.{}.MessageBody'.format(match.group(1)))[0]
}
if any(not re.match(r'^[\w-]{1,80}$', entry['Id']) for entry in entries.values()):
return self._error('AWS.SimpleQueueService.InvalidBatchEntryId',
'A batch entry id can only contain alphanumeric characters, '
'hyphens and underscores. It can be at most 80 letters long.')
body_length = next(
(len(entry['MessageBody']) for entry in entries.values() if len(entry['MessageBody']) > MAXIMUM_MESSAGE_LENGTH),
False
)
if body_length:
return self._error('AWS.SimpleQueueService.BatchRequestTooLong',
'Batch requests cannot be longer than 262144 bytes. '
'You have sent {} bytes.'.format(body_length))
duplicate_id = self._get_first_duplicate_id([entry['Id'] for entry in entries.values()])
if duplicate_id:
return self._error('AWS.SimpleQueueService.BatchEntryIdsNotDistinct',
'Id {} repeated.'.format(duplicate_id))
if len(entries) > 10:
return self._error('AWS.SimpleQueueService.TooManyEntriesInBatchRequest',
'Maximum number of entries per request are 10. '
'You have sent 11.')
messages = []
for index, entry in entries.items():
# Loop through looking for messages
delay_key = 'SendMessageBatchRequestEntry.{0}.DelaySeconds'.format(
index)
delay_seconds = self.querystring.get(delay_key, [None])[0]
message = self.sqs_backend.send_message(
queue_name, message_body[0], delay_seconds=delay_seconds)
message.user_id = message_user_id
queue_name, entry['MessageBody'], delay_seconds=delay_seconds)
message.user_id = entry['Id']
message_attributes = parse_message_attributes(
self.querystring, base='SendMessageBatchRequestEntry.{0}.'.format(index))
@ -273,6 +295,14 @@ class SQSResponse(BaseResponse):
template = self.response_template(SEND_MESSAGE_BATCH_RESPONSE)
return template.render(messages=messages)
def _get_first_duplicate_id(self, ids):
unique_ids = set()
for id in ids:
if id in unique_ids:
return id
unique_ids.add(id)
return None
def delete_message(self):
queue_name = self._get_queue_name()
receipt_handle = self.querystring.get("ReceiptHandle")[0]
@ -321,10 +351,7 @@ class SQSResponse(BaseResponse):
def receive_message(self):
queue_name = self._get_queue_name()
try:
queue = self.sqs_backend.get_queue(queue_name)
except QueueDoesNotExist as e:
return self._error('QueueDoesNotExist', e.description)
queue = self.sqs_backend.get_queue(queue_name)
try:
message_count = int(self.querystring.get("MaxNumberOfMessages")[0])
@ -406,11 +433,7 @@ class SQSResponse(BaseResponse):
queue_name = self._get_queue_name()
tag_keys = self._get_multi_param('TagKey')
try:
self.sqs_backend.untag_queue(queue_name, tag_keys)
except QueueDoesNotExist as e:
return self._error('AWS.SimpleQueueService.NonExistentQueue',
e.description)
self.sqs_backend.untag_queue(queue_name, tag_keys)
template = self.response_template(UNTAG_QUEUE_RESPONSE)
return template.render()

View File

@ -1134,7 +1134,7 @@ def test_invoke_function_from_sqs_exception():
if 'I failed!' in event['message']:
messages = queue.receive_messages(MaxNumberOfMessages=10)
# Verify messages are still visible and unprocessed
assert len(messages) is 3
assert len(messages) == 3
return
time.sleep(1)

View File

@ -2705,6 +2705,31 @@ def test_item_size_is_under_400KB():
Item={'id': {'S': 'foo'}, 'itemlist': {'L': [{'M': {'item1': {'S': large_item}}}]}})
@mock_dynamodb2
# https://docs.aws.amazon.com/amazondynamodb/latest/APIReference/API_Query.html#DDB-Query-request-KeyConditionExpression
def test_hash_key_cannot_use_begins_with_operations():
dynamodb = boto3.resource('dynamodb')
table = dynamodb.create_table(
TableName='test-table',
KeySchema=[{'AttributeName': 'key', 'KeyType': 'HASH'}],
AttributeDefinitions=[{'AttributeName': 'key', 'AttributeType': 'S'}],
ProvisionedThroughput={'ReadCapacityUnits': 1, 'WriteCapacityUnits': 1})
items = [{'key': 'prefix-$LATEST', 'value': '$LATEST'},
{'key': 'prefix-DEV', 'value': 'DEV'},
{'key': 'prefix-PROD', 'value': 'PROD'}]
with table.batch_writer() as batch:
for item in items:
batch.put_item(Item=item)
table = dynamodb.Table('test-table')
with assert_raises(ClientError) as ex:
table.query(KeyConditionExpression=Key('key').begins_with('prefix-'))
ex.exception.response['Error']['Code'].should.equal('ValidationException')
ex.exception.response['Error']['Message'].should.equal('Query key condition not supported')
def assert_failure_due_to_item_size(func, **kwargs):
with assert_raises(ClientError) as ex:
func(**kwargs)

View File

@ -9,6 +9,8 @@ import sure # noqa
import sys
from boto.exception import BotoServerError
from botocore.exceptions import ClientError
from dateutil.tz import tzutc
from moto import mock_iam, mock_iam_deprecated
from moto.iam.models import aws_managed_policies
from nose.tools import assert_raises, assert_equals
@ -338,6 +340,15 @@ def test_create_policy():
response['Policy']['Arn'].should.equal("arn:aws:iam::123456789012:policy/TestCreatePolicy")
@mock_iam
def test_delete_policy():
conn = boto3.client('iam', region_name='us-east-1')
response = conn.create_policy(PolicyName="TestCreatePolicy", PolicyDocument=MOCK_POLICY)
[pol['PolicyName'] for pol in conn.list_policies(Scope='Local')['Policies']].should.equal(['TestCreatePolicy'])
conn.delete_policy(PolicyArn=response['Policy']['Arn'])
assert conn.list_policies(Scope='Local')['Policies'].should.be.empty
@mock_iam
def test_create_policy_versions():
conn = boto3.client('iam', region_name='us-east-1')
@ -713,7 +724,7 @@ def test_mfa_devices():
@mock_iam_deprecated()
def test_delete_user():
def test_delete_user_deprecated():
conn = boto.connect_iam()
with assert_raises(BotoServerError):
conn.delete_user('my-user')
@ -721,6 +732,17 @@ def test_delete_user():
conn.delete_user('my-user')
@mock_iam()
def test_delete_user():
conn = boto3.client('iam', region_name='us-east-1')
with assert_raises(ClientError):
conn.delete_user(UserName='my-user')
conn.create_user(UserName='my-user')
[user['UserName'] for user in conn.list_users()['Users']].should.equal(['my-user'])
conn.delete_user(UserName='my-user')
assert conn.list_users()['Users'].should.be.empty
@mock_iam_deprecated()
def test_generate_credential_report():
conn = boto.connect_iam()
@ -1545,3 +1567,251 @@ def test_create_role_with_permissions_boundary():
# Ensure the PermissionsBoundary is included in role listing as well
conn.list_roles().get('Roles')[0].get('PermissionsBoundary').should.equal(expected)
@mock_iam
def test_create_open_id_connect_provider():
client = boto3.client('iam', region_name='us-east-1')
response = client.create_open_id_connect_provider(
Url='https://example.com',
ThumbprintList=[] # even it is required to provide at least one thumbprint, AWS accepts an empty list
)
response['OpenIDConnectProviderArn'].should.equal(
'arn:aws:iam::123456789012:oidc-provider/example.com'
)
response = client.create_open_id_connect_provider(
Url='http://example.org',
ThumbprintList=[
'b' * 40
],
ClientIDList=[
'b'
]
)
response['OpenIDConnectProviderArn'].should.equal(
'arn:aws:iam::123456789012:oidc-provider/example.org'
)
response = client.create_open_id_connect_provider(
Url='http://example.org/oidc',
ThumbprintList=[]
)
response['OpenIDConnectProviderArn'].should.equal(
'arn:aws:iam::123456789012:oidc-provider/example.org/oidc'
)
response = client.create_open_id_connect_provider(
Url='http://example.org/oidc-query?test=true',
ThumbprintList=[]
)
response['OpenIDConnectProviderArn'].should.equal(
'arn:aws:iam::123456789012:oidc-provider/example.org/oidc-query'
)
@mock_iam
def test_create_open_id_connect_provider_errors():
client = boto3.client('iam', region_name='us-east-1')
client.create_open_id_connect_provider(
Url='https://example.com',
ThumbprintList=[]
)
client.create_open_id_connect_provider.when.called_with(
Url='https://example.com',
ThumbprintList=[]
).should.throw(
ClientError,
'Unknown'
)
client.create_open_id_connect_provider.when.called_with(
Url='example.org',
ThumbprintList=[]
).should.throw(
ClientError,
'Invalid Open ID Connect Provider URL'
)
client.create_open_id_connect_provider.when.called_with(
Url='example',
ThumbprintList=[]
).should.throw(
ClientError,
'Invalid Open ID Connect Provider URL'
)
client.create_open_id_connect_provider.when.called_with(
Url='http://example.org',
ThumbprintList=[
'a' * 40,
'b' * 40,
'c' * 40,
'd' * 40,
'e' * 40,
'f' * 40,
]
).should.throw(
ClientError,
'Thumbprint list must contain fewer than 5 entries.'
)
too_many_client_ids = ['{}'.format(i) for i in range(101)]
client.create_open_id_connect_provider.when.called_with(
Url='http://example.org',
ThumbprintList=[],
ClientIDList=too_many_client_ids
).should.throw(
ClientError,
'Cannot exceed quota for ClientIdsPerOpenIdConnectProvider: 100'
)
too_long_url = 'b' * 256
too_long_thumbprint = 'b' * 41
too_long_client_id = 'b' * 256
client.create_open_id_connect_provider.when.called_with(
Url=too_long_url,
ThumbprintList=[
too_long_thumbprint
],
ClientIDList=[
too_long_client_id
]
).should.throw(
ClientError,
'3 validation errors detected: '
'Value "{0}" at "clientIDList" failed to satisfy constraint: '
'Member must satisfy constraint: '
'[Member must have length less than or equal to 255, '
'Member must have length greater than or equal to 1]; '
'Value "{1}" at "thumbprintList" failed to satisfy constraint: '
'Member must satisfy constraint: '
'[Member must have length less than or equal to 40, '
'Member must have length greater than or equal to 40]; '
'Value "{2}" at "url" failed to satisfy constraint: '
'Member must have length less than or equal to 255'.format([too_long_client_id], [too_long_thumbprint], too_long_url)
)
@mock_iam
def test_delete_open_id_connect_provider():
client = boto3.client('iam', region_name='us-east-1')
response = client.create_open_id_connect_provider(
Url='https://example.com',
ThumbprintList=[]
)
open_id_arn = response['OpenIDConnectProviderArn']
client.delete_open_id_connect_provider(
OpenIDConnectProviderArn=open_id_arn
)
client.get_open_id_connect_provider.when.called_with(
OpenIDConnectProviderArn=open_id_arn
).should.throw(
ClientError,
'OpenIDConnect Provider not found for arn {}'.format(open_id_arn)
)
# deleting a non existing provider should be successful
client.delete_open_id_connect_provider(
OpenIDConnectProviderArn=open_id_arn
)
@mock_iam
def test_get_open_id_connect_provider():
client = boto3.client('iam', region_name='us-east-1')
response = client.create_open_id_connect_provider(
Url='https://example.com',
ThumbprintList=[
'b' * 40
],
ClientIDList=[
'b'
]
)
open_id_arn = response['OpenIDConnectProviderArn']
response = client.get_open_id_connect_provider(
OpenIDConnectProviderArn=open_id_arn
)
response['Url'].should.equal('example.com')
response['ThumbprintList'].should.equal([
'b' * 40
])
response['ClientIDList'].should.equal([
'b'
])
response.should.have.key('CreateDate').should.be.a(datetime)
@mock_iam
def test_get_open_id_connect_provider_errors():
client = boto3.client('iam', region_name='us-east-1')
response = client.create_open_id_connect_provider(
Url='https://example.com',
ThumbprintList=[
'b' * 40
],
ClientIDList=[
'b'
]
)
open_id_arn = response['OpenIDConnectProviderArn']
client.get_open_id_connect_provider.when.called_with(
OpenIDConnectProviderArn=open_id_arn + '-not-existing'
).should.throw(
ClientError,
'OpenIDConnect Provider not found for arn {}'.format(open_id_arn + '-not-existing')
)
@mock_iam
def test_list_open_id_connect_providers():
client = boto3.client('iam', region_name='us-east-1')
response = client.create_open_id_connect_provider(
Url='https://example.com',
ThumbprintList=[]
)
open_id_arn_1 = response['OpenIDConnectProviderArn']
response = client.create_open_id_connect_provider(
Url='http://example.org',
ThumbprintList=[
'b' * 40
],
ClientIDList=[
'b'
]
)
open_id_arn_2 = response['OpenIDConnectProviderArn']
response = client.create_open_id_connect_provider(
Url='http://example.org/oidc',
ThumbprintList=[]
)
open_id_arn_3 = response['OpenIDConnectProviderArn']
response = client.list_open_id_connect_providers()
sorted(response['OpenIDConnectProviderList'], key=lambda i: i['Arn']).should.equal(
[
{
'Arn': open_id_arn_1
},
{
'Arn': open_id_arn_2
},
{
'Arn': open_id_arn_3
}
]
)

View File

@ -39,6 +39,20 @@ def test_create_database():
db_instance['VpcSecurityGroups'][0]['VpcSecurityGroupId'].should.equal('sg-123456')
@mock_rds2
def test_create_database_no_allocated_storage():
conn = boto3.client('rds', region_name='us-west-2')
database = conn.create_db_instance(
DBInstanceIdentifier='db-master-1',
Engine='postgres',
DBName='staging-postgres',
DBInstanceClass='db.m1.small')
db_instance = database['DBInstance']
db_instance['Engine'].should.equal('postgres')
db_instance['StorageType'].should.equal('gp2')
db_instance['AllocatedStorage'].should.equal(20)
@mock_rds2
def test_create_database_non_existing_option_group():
conn = boto3.client('rds', region_name='us-west-2')

View File

@ -1534,6 +1534,18 @@ def test_boto3_get_object():
e.exception.response['Error']['Code'].should.equal('NoSuchKey')
@mock_s3
def test_boto3_get_missing_object_with_part_number():
s3 = boto3.resource('s3', region_name='us-east-1')
s3.create_bucket(Bucket="blah")
with assert_raises(ClientError) as e:
s3.Object('blah', 'hello.txt').meta.client.head_object(
Bucket='blah', Key='hello.txt', PartNumber=123)
e.exception.response['Error']['Code'].should.equal('404')
@mock_s3
def test_boto3_head_object_with_versioning():
s3 = boto3.resource('s3', region_name='us-east-1')
@ -2587,6 +2599,160 @@ def test_boto3_put_object_tagging():
resp['ResponseMetadata']['HTTPStatusCode'].should.equal(200)
@mock_s3
def test_boto3_put_object_tagging_on_earliest_version():
s3 = boto3.client('s3', region_name='us-east-1')
bucket_name = 'mybucket'
key = 'key-with-tags'
s3.create_bucket(Bucket=bucket_name)
s3_resource = boto3.resource('s3')
bucket_versioning = s3_resource.BucketVersioning(bucket_name)
bucket_versioning.enable()
bucket_versioning.status.should.equal('Enabled')
with assert_raises(ClientError) as err:
s3.put_object_tagging(
Bucket=bucket_name,
Key=key,
Tagging={'TagSet': [
{'Key': 'item1', 'Value': 'foo'},
{'Key': 'item2', 'Value': 'bar'},
]}
)
e = err.exception
e.response['Error'].should.equal({
'Code': 'NoSuchKey',
'Message': 'The specified key does not exist.',
'RequestID': '7a62c49f-347e-4fc4-9331-6e8eEXAMPLE',
})
s3.put_object(
Bucket=bucket_name,
Key=key,
Body='test'
)
s3.put_object(
Bucket=bucket_name,
Key=key,
Body='test_updated'
)
object_versions = list(s3_resource.Bucket(bucket_name).object_versions.all())
first_object = object_versions[0]
second_object = object_versions[1]
resp = s3.put_object_tagging(
Bucket=bucket_name,
Key=key,
Tagging={'TagSet': [
{'Key': 'item1', 'Value': 'foo'},
{'Key': 'item2', 'Value': 'bar'},
]},
VersionId=first_object.id
)
resp['ResponseMetadata']['HTTPStatusCode'].should.equal(200)
# Older version has tags while the most recent does not
resp = s3.get_object_tagging(Bucket=bucket_name, Key=key, VersionId=first_object.id)
resp['ResponseMetadata']['HTTPStatusCode'].should.equal(200)
resp['TagSet'].should.equal(
[
{'Key': 'item1', 'Value': 'foo'},
{'Key': 'item2', 'Value': 'bar'}
]
)
resp = s3.get_object_tagging(Bucket=bucket_name, Key=key, VersionId=second_object.id)
resp['ResponseMetadata']['HTTPStatusCode'].should.equal(200)
resp['TagSet'].should.equal([])
@mock_s3
def test_boto3_put_object_tagging_on_both_version():
s3 = boto3.client('s3', region_name='us-east-1')
bucket_name = 'mybucket'
key = 'key-with-tags'
s3.create_bucket(Bucket=bucket_name)
s3_resource = boto3.resource('s3')
bucket_versioning = s3_resource.BucketVersioning(bucket_name)
bucket_versioning.enable()
bucket_versioning.status.should.equal('Enabled')
with assert_raises(ClientError) as err:
s3.put_object_tagging(
Bucket=bucket_name,
Key=key,
Tagging={'TagSet': [
{'Key': 'item1', 'Value': 'foo'},
{'Key': 'item2', 'Value': 'bar'},
]}
)
e = err.exception
e.response['Error'].should.equal({
'Code': 'NoSuchKey',
'Message': 'The specified key does not exist.',
'RequestID': '7a62c49f-347e-4fc4-9331-6e8eEXAMPLE',
})
s3.put_object(
Bucket=bucket_name,
Key=key,
Body='test'
)
s3.put_object(
Bucket=bucket_name,
Key=key,
Body='test_updated'
)
object_versions = list(s3_resource.Bucket(bucket_name).object_versions.all())
first_object = object_versions[0]
second_object = object_versions[1]
resp = s3.put_object_tagging(
Bucket=bucket_name,
Key=key,
Tagging={'TagSet': [
{'Key': 'item1', 'Value': 'foo'},
{'Key': 'item2', 'Value': 'bar'},
]},
VersionId=first_object.id
)
resp['ResponseMetadata']['HTTPStatusCode'].should.equal(200)
resp = s3.put_object_tagging(
Bucket=bucket_name,
Key=key,
Tagging={'TagSet': [
{'Key': 'item1', 'Value': 'baz'},
{'Key': 'item2', 'Value': 'bin'},
]},
VersionId=second_object.id
)
resp['ResponseMetadata']['HTTPStatusCode'].should.equal(200)
resp = s3.get_object_tagging(Bucket=bucket_name, Key=key, VersionId=first_object.id)
resp['ResponseMetadata']['HTTPStatusCode'].should.equal(200)
resp['TagSet'].should.equal(
[
{'Key': 'item1', 'Value': 'foo'},
{'Key': 'item2', 'Value': 'bar'}
]
)
resp = s3.get_object_tagging(Bucket=bucket_name, Key=key, VersionId=second_object.id)
resp['ResponseMetadata']['HTTPStatusCode'].should.equal(200)
resp['TagSet'].should.equal(
[
{'Key': 'item1', 'Value': 'baz'},
{'Key': 'item2', 'Value': 'bin'}
]
)
@mock_s3
def test_boto3_put_object_tagging_with_single_tag():
s3 = boto3.client('s3', region_name='us-east-1')

View File

@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
import boto3
@ -8,7 +9,7 @@ import string
import pytz
from datetime import datetime
import sure # noqa
from nose.tools import assert_raises
from nose.tools import assert_raises, assert_equal
from six import b
DEFAULT_SECRET_NAME = 'test-secret'
@ -38,9 +39,14 @@ def test_get_secret_value_binary():
def test_get_secret_that_does_not_exist():
conn = boto3.client('secretsmanager', region_name='us-west-2')
with assert_raises(ClientError):
with assert_raises(ClientError) as cm:
result = conn.get_secret_value(SecretId='i-dont-exist')
assert_equal(
u"Secrets Manager can\u2019t find the specified secret.",
cm.exception.response['Error']['Message']
)
@mock_secretsmanager
def test_get_secret_that_does_not_match():
@ -48,9 +54,14 @@ def test_get_secret_that_does_not_match():
create_secret = conn.create_secret(Name='java-util-test-password',
SecretString="foosecret")
with assert_raises(ClientError):
with assert_raises(ClientError) as cm:
result = conn.get_secret_value(SecretId='i-dont-match')
assert_equal(
u"Secrets Manager can\u2019t find the specified secret.",
cm.exception.response['Error']['Message']
)
@mock_secretsmanager
def test_get_secret_value_that_is_marked_deleted():
@ -65,6 +76,21 @@ def test_get_secret_value_that_is_marked_deleted():
result = conn.get_secret_value(SecretId='test-secret')
@mock_secretsmanager
def test_get_secret_that_has_no_value():
conn = boto3.client('secretsmanager', region_name='us-west-2')
create_secret = conn.create_secret(Name="java-util-test-password")
with assert_raises(ClientError) as cm:
result = conn.get_secret_value(SecretId='java-util-test-password')
assert_equal(
u"Secrets Manager can\u2019t find the specified secret value for staging label: AWSCURRENT",
cm.exception.response['Error']['Message']
)
@mock_secretsmanager
def test_create_secret():
conn = boto3.client('secretsmanager', region_name='us-east-1')

View File

@ -1,3 +1,4 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
import json
@ -49,7 +50,7 @@ def test_get_secret_that_does_not_exist():
"X-Amz-Target": "secretsmanager.GetSecretValue"},
)
json_data = json.loads(get_secret.data.decode("utf-8"))
assert json_data['message'] == "Secrets Manager can't find the specified secret"
assert json_data['message'] == u"Secrets Manager can\u2019t find the specified secret."
assert json_data['__type'] == 'ResourceNotFoundException'
@mock_secretsmanager
@ -70,7 +71,27 @@ def test_get_secret_that_does_not_match():
"X-Amz-Target": "secretsmanager.GetSecretValue"},
)
json_data = json.loads(get_secret.data.decode("utf-8"))
assert json_data['message'] == "Secrets Manager can't find the specified secret"
assert json_data['message'] == u"Secrets Manager can\u2019t find the specified secret."
assert json_data['__type'] == 'ResourceNotFoundException'
@mock_secretsmanager
def test_get_secret_that_has_no_value():
backend = server.create_backend_app('secretsmanager')
test_client = backend.test_client()
create_secret = test_client.post('/',
data={"Name": DEFAULT_SECRET_NAME},
headers={
"X-Amz-Target": "secretsmanager.CreateSecret"},
)
get_secret = test_client.post('/',
data={"SecretId": DEFAULT_SECRET_NAME},
headers={
"X-Amz-Target": "secretsmanager.GetSecretValue"},
)
json_data = json.loads(get_secret.data.decode("utf-8"))
assert json_data['message'] == u"Secrets Manager can\u2019t find the specified secret value for staging label: AWSCURRENT"
assert json_data['__type'] == 'ResourceNotFoundException'
@mock_secretsmanager
@ -158,7 +179,7 @@ def test_describe_secret_that_does_not_exist():
)
json_data = json.loads(describe_secret.data.decode("utf-8"))
assert json_data['message'] == "Secrets Manager can't find the specified secret"
assert json_data['message'] == u"Secrets Manager can\u2019t find the specified secret."
assert json_data['__type'] == 'ResourceNotFoundException'
@mock_secretsmanager
@ -182,7 +203,7 @@ def test_describe_secret_that_does_not_match():
)
json_data = json.loads(describe_secret.data.decode("utf-8"))
assert json_data['message'] == "Secrets Manager can't find the specified secret"
assert json_data['message'] == u"Secrets Manager can\u2019t find the specified secret."
assert json_data['__type'] == 'ResourceNotFoundException'
@mock_secretsmanager
@ -283,7 +304,7 @@ def test_rotate_secret_that_does_not_exist():
)
json_data = json.loads(rotate_secret.data.decode("utf-8"))
assert json_data['message'] == "Secrets Manager can't find the specified secret"
assert json_data['message'] == u"Secrets Manager can\u2019t find the specified secret."
assert json_data['__type'] == 'ResourceNotFoundException'
@mock_secretsmanager
@ -307,7 +328,7 @@ def test_rotate_secret_that_does_not_match():
)
json_data = json.loads(rotate_secret.data.decode("utf-8"))
assert json_data['message'] == "Secrets Manager can't find the specified secret"
assert json_data['message'] == u"Secrets Manager can\u2019t find the specified secret."
assert json_data['__type'] == 'ResourceNotFoundException'
@mock_secretsmanager

View File

@ -181,6 +181,35 @@ def test_subscription_paging():
int(DEFAULT_PAGE_SIZE / 3))
topic1_subscriptions.shouldnt.have("NextToken")
@mock_sns
def test_subscribe_attributes():
client = boto3.client('sns', region_name='us-east-1')
client.create_topic(Name="some-topic")
resp = client.create_topic(Name="some-topic")
arn = resp['TopicArn']
resp = client.subscribe(
TopicArn=arn,
Protocol='http',
Endpoint='http://test.com'
)
attributes = client.get_subscription_attributes(
SubscriptionArn=resp['SubscriptionArn']
)
attributes.should.contain('Attributes')
attributes['Attributes'].should.contain('PendingConfirmation')
attributes['Attributes']['PendingConfirmation'].should.equal('false')
attributes['Attributes'].should.contain('Endpoint')
attributes['Attributes']['Endpoint'].should.equal('http://test.com')
attributes['Attributes'].should.contain('TopicArn')
attributes['Attributes']['TopicArn'].should.equal(arn)
attributes['Attributes'].should.contain('Protocol')
attributes['Attributes']['Protocol'].should.equal('http')
attributes['Attributes'].should.contain('SubscriptionArn')
attributes['Attributes']['SubscriptionArn'].should.equal(resp['SubscriptionArn'])
@mock_sns
def test_creating_subscription_with_attributes():

View File

@ -882,6 +882,127 @@ def test_delete_message_after_visibility_timeout():
assert new_queue.count() == 0
@mock_sqs
def test_send_message_batch_errors():
client = boto3.client('sqs', region_name = 'us-east-1')
response = client.create_queue(QueueName='test-queue-with-tags')
queue_url = response['QueueUrl']
client.send_message_batch.when.called_with(
QueueUrl=queue_url + '-not-existing',
Entries=[
{
'Id': 'id_1',
'MessageBody': 'body_1'
}
]
).should.throw(
ClientError,
'The specified queue does not exist for this wsdl version.'
)
client.send_message_batch.when.called_with(
QueueUrl=queue_url,
Entries=[]
).should.throw(
ClientError,
'There should be at least one SendMessageBatchRequestEntry in the request.'
)
client.send_message_batch.when.called_with(
QueueUrl=queue_url,
Entries=[
{
'Id': '',
'MessageBody': 'body_1'
}
]
).should.throw(
ClientError,
'A batch entry id can only contain alphanumeric characters, '
'hyphens and underscores. It can be at most 80 letters long.'
)
client.send_message_batch.when.called_with(
QueueUrl=queue_url,
Entries=[
{
'Id': '.!@#$%^&*()+=',
'MessageBody': 'body_1'
}
]
).should.throw(
ClientError,
'A batch entry id can only contain alphanumeric characters, '
'hyphens and underscores. It can be at most 80 letters long.'
)
client.send_message_batch.when.called_with(
QueueUrl=queue_url,
Entries=[
{
'Id': 'i' * 81,
'MessageBody': 'body_1'
}
]
).should.throw(
ClientError,
'A batch entry id can only contain alphanumeric characters, '
'hyphens and underscores. It can be at most 80 letters long.'
)
client.send_message_batch.when.called_with(
QueueUrl=queue_url,
Entries=[
{
'Id': 'id_1',
'MessageBody': 'b' * 262145
}
]
).should.throw(
ClientError,
'Batch requests cannot be longer than 262144 bytes. '
'You have sent 262145 bytes.'
)
# only the first duplicated Id is reported
client.send_message_batch.when.called_with(
QueueUrl=queue_url,
Entries=[
{
'Id': 'id_1',
'MessageBody': 'body_1'
},
{
'Id': 'id_2',
'MessageBody': 'body_2'
},
{
'Id': 'id_2',
'MessageBody': 'body_2'
},
{
'Id': 'id_1',
'MessageBody': 'body_1'
}
]
).should.throw(
ClientError,
'Id id_2 repeated.'
)
entries = [{'Id': 'id_{}'.format(i), 'MessageBody': 'body_{}'.format(i)} for i in range(11)]
client.send_message_batch.when.called_with(
QueueUrl=queue_url,
Entries=entries
).should.throw(
ClientError,
'Maximum number of entries per request are 10. '
'You have sent 11.'
)
@mock_sqs
def test_batch_change_message_visibility():
if os.environ.get('TEST_SERVER_MODE', 'false').lower() == 'true':
@ -987,6 +1108,73 @@ def test_tags():
})
@mock_sqs
def test_list_queue_tags_errors():
client = boto3.client('sqs', region_name='us-east-1')
response = client.create_queue(
QueueName='test-queue-with-tags',
tags={
'tag_key_1': 'tag_value_X'
}
)
queue_url = response['QueueUrl']
client.list_queue_tags.when.called_with(
QueueUrl=queue_url + '-not-existing',
).should.throw(
ClientError,
'The specified queue does not exist for this wsdl version.'
)
@mock_sqs
def test_tag_queue_errors():
client = boto3.client('sqs', region_name='us-east-1')
response = client.create_queue(
QueueName='test-queue-with-tags',
tags={
'tag_key_1': 'tag_value_X'
}
)
queue_url = response['QueueUrl']
client.tag_queue.when.called_with(
QueueUrl=queue_url + '-not-existing',
Tags={
'tag_key_1': 'tag_value_1'
}
).should.throw(
ClientError,
'The specified queue does not exist for this wsdl version.'
)
client.tag_queue.when.called_with(
QueueUrl=queue_url,
Tags={}
).should.throw(
ClientError,
'The request must contain the parameter Tags.'
)
too_many_tags = {'tag_key_{}'.format(i): 'tag_value_{}'.format(i) for i in range(51)}
client.tag_queue.when.called_with(
QueueUrl=queue_url,
Tags=too_many_tags
).should.throw(
ClientError,
'Too many tags added for queue test-queue-with-tags.'
)
# when the request fails, the tags should not be updated
client.list_queue_tags(QueueUrl=queue_url)['Tags'].should.equal(
{
'tag_key_1': 'tag_value_X'
}
)
@mock_sqs
def test_untag_queue_errors():
client = boto3.client('sqs', region_name='us-east-1')
@ -1006,7 +1194,7 @@ def test_untag_queue_errors():
]
).should.throw(
ClientError,
"The specified queue does not exist for this wsdl version."
'The specified queue does not exist for this wsdl version.'
)
client.untag_queue.when.called_with(