diff --git a/.gitignore b/.gitignore
index 7f57e98e9..cfda51440 100644
--- a/.gitignore
+++ b/.gitignore
@@ -15,4 +15,4 @@ python_env
.ropeproject/
.pytest_cache/
venv/
-
+.vscode/
diff --git a/.travis.yml b/.travis.yml
index de22818b8..3a5de0fa2 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -23,6 +23,8 @@ matrix:
sudo: true
before_install:
- export BOTO_CONFIG=/dev/null
+ - export AWS_SECRET_ACCESS_KEY=foobar_secret
+ - export AWS_ACCESS_KEY_ID=foobar_key
install:
# We build moto first so the docker container doesn't try to compile it as well, also note we don't use
# -d for docker run so the logs show up in travis
@@ -32,8 +34,6 @@ install:
if [ "$TEST_SERVER_MODE" = "true" ]; then
docker run --rm -t --name motoserver -e TEST_SERVER_MODE=true -e AWS_SECRET_ACCESS_KEY=server_secret -e AWS_ACCESS_KEY_ID=server_key -v `pwd`:/moto -p 5000:5000 -v /var/run/docker.sock:/var/run/docker.sock python:${TRAVIS_PYTHON_VERSION}-stretch /moto/travis_moto_server.sh &
- export AWS_SECRET_ACCESS_KEY=foobar_secret
- export AWS_ACCESS_KEY_ID=foobar_key
fi
travis_retry pip install boto==2.45.0
travis_retry pip install boto3
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 7f7ee4448..f42619b33 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,11 @@
Moto Changelog
===================
+1.3.7
+-----
+
+ * Switch from mocking requests to using before-send for AWS calls
+
1.3.6
-----
diff --git a/README.md b/README.md
index 791226d6b..d6e9f30a1 100644
--- a/README.md
+++ b/README.md
@@ -259,7 +259,7 @@ It uses flask, which isn't a default dependency. You can install the
server 'extra' package with:
```python
-pip install moto[server]
+pip install "moto[server]"
```
You can then start it running a service:
diff --git a/moto/__init__.py b/moto/__init__.py
index 6992c535e..dd3593d5d 100644
--- a/moto/__init__.py
+++ b/moto/__init__.py
@@ -3,7 +3,7 @@ import logging
# logging.getLogger('boto').setLevel(logging.CRITICAL)
__title__ = 'moto'
-__version__ = '1.3.6'
+__version__ = '1.3.7'
from .acm import mock_acm # flake8: noqa
from .apigateway import mock_apigateway, mock_apigateway_deprecated # flake8: noqa
diff --git a/moto/apigateway/models.py b/moto/apigateway/models.py
index db4746a0e..41a49e361 100644
--- a/moto/apigateway/models.py
+++ b/moto/apigateway/models.py
@@ -10,6 +10,7 @@ from boto3.session import Session
import responses
from moto.core import BaseBackend, BaseModel
from .utils import create_id
+from moto.core.utils import path_url
from .exceptions import StageNotFoundException, ApiKeyNotFoundException
STAGE_URL = "https://{api_id}.execute-api.{region_name}.amazonaws.com/{stage_name}"
@@ -372,7 +373,8 @@ class RestAPI(BaseModel):
# TODO deal with no matching resource
def resource_callback(self, request):
- path_after_stage_name = '/'.join(request.path_url.split("/")[2:])
+ path = path_url(request.url)
+ path_after_stage_name = '/'.join(path.split("/")[2:])
if not path_after_stage_name:
path_after_stage_name = '/'
diff --git a/moto/autoscaling/responses.py b/moto/autoscaling/responses.py
index 5586c51dd..845db0136 100644
--- a/moto/autoscaling/responses.py
+++ b/moto/autoscaling/responses.py
@@ -508,6 +508,15 @@ DESCRIBE_AUTOSCALING_GROUPS_TEMPLATE = """
{% endif %}
+ {% if group.target_group_arns %}
+
+ {% for target_group_arn in group.target_group_arns %}
+ {{ target_group_arn }}
+ {% endfor %}
+
+ {% else %}
+
+ {% endif %}
{{ group.min_size }}
{% if group.vpc_zone_identifier %}
{{ group.vpc_zone_identifier }}
diff --git a/moto/awslambda/responses.py b/moto/awslambda/responses.py
index 2c8a54523..1a9a4df83 100644
--- a/moto/awslambda/responses.py
+++ b/moto/awslambda/responses.py
@@ -7,7 +7,7 @@ try:
except ImportError:
from urllib.parse import unquote
-from moto.core.utils import amz_crc32, amzn_request_id
+from moto.core.utils import amz_crc32, amzn_request_id, path_url
from moto.core.responses import BaseResponse
from .models import lambda_backends
@@ -94,7 +94,7 @@ class LambdaResponse(BaseResponse):
return self._add_policy(request, full_url, headers)
def _add_policy(self, request, full_url, headers):
- path = request.path if hasattr(request, 'path') else request.path_url
+ path = request.path if hasattr(request, 'path') else path_url(request.url)
function_name = path.split('/')[-2]
if self.lambda_backend.get_function(function_name):
policy = request.body.decode('utf8')
@@ -104,7 +104,7 @@ class LambdaResponse(BaseResponse):
return 404, {}, "{}"
def _get_policy(self, request, full_url, headers):
- path = request.path if hasattr(request, 'path') else request.path_url
+ path = request.path if hasattr(request, 'path') else path_url(request.url)
function_name = path.split('/')[-2]
if self.lambda_backend.get_function(function_name):
lambda_function = self.lambda_backend.get_function(function_name)
diff --git a/moto/cognitoidp/models.py b/moto/cognitoidp/models.py
index 476d470b9..edeb7b128 100644
--- a/moto/cognitoidp/models.py
+++ b/moto/cognitoidp/models.py
@@ -1,6 +1,8 @@
from __future__ import unicode_literals
import datetime
+import functools
+import itertools
import json
import os
import time
@@ -20,6 +22,39 @@ UserStatus = {
}
+def paginate(limit, start_arg="next_token", limit_arg="max_results"):
+ """Returns a limited result list, and an offset into list of remaining items
+
+ Takes the next_token, and max_results kwargs given to a function and handles
+ the slicing of the results. The kwarg `next_token` is the offset into the
+ list to begin slicing from. `max_results` is the size of the result required
+
+ If the max_results is not supplied then the `limit` parameter is used as a
+ default
+
+ :param limit_arg: the name of argument in the decorated function that
+ controls amount of items returned
+ :param start_arg: the name of the argument in the decorated that provides
+ the starting offset
+ :param limit: A default maximum items to return
+ :return: a tuple containing a list of items, and the offset into the list
+ """
+ default_start = 0
+
+ def outer_wrapper(func):
+ @functools.wraps(func)
+ def wrapper(*args, **kwargs):
+ start = int(default_start if kwargs.get(start_arg) is None else kwargs[start_arg])
+ lim = int(limit if kwargs.get(limit_arg) is None else kwargs[limit_arg])
+ stop = start + lim
+ result = func(*args, **kwargs)
+ limited_results = list(itertools.islice(result, start, stop))
+ next_token = stop if stop < len(result) else None
+ return limited_results, next_token
+ return wrapper
+ return outer_wrapper
+
+
class CognitoIdpUserPool(BaseModel):
def __init__(self, region, name, extended_config):
@@ -242,7 +277,8 @@ class CognitoIdpBackend(BaseBackend):
self.user_pools[user_pool.id] = user_pool
return user_pool
- def list_user_pools(self):
+ @paginate(60)
+ def list_user_pools(self, max_results=None, next_token=None):
return self.user_pools.values()
def describe_user_pool(self, user_pool_id):
@@ -289,7 +325,8 @@ class CognitoIdpBackend(BaseBackend):
user_pool.clients[user_pool_client.id] = user_pool_client
return user_pool_client
- def list_user_pool_clients(self, user_pool_id):
+ @paginate(60)
+ def list_user_pool_clients(self, user_pool_id, max_results=None, next_token=None):
user_pool = self.user_pools.get(user_pool_id)
if not user_pool:
raise ResourceNotFoundError(user_pool_id)
@@ -339,7 +376,8 @@ class CognitoIdpBackend(BaseBackend):
user_pool.identity_providers[name] = identity_provider
return identity_provider
- def list_identity_providers(self, user_pool_id):
+ @paginate(60)
+ def list_identity_providers(self, user_pool_id, max_results=None, next_token=None):
user_pool = self.user_pools.get(user_pool_id)
if not user_pool:
raise ResourceNotFoundError(user_pool_id)
@@ -387,7 +425,8 @@ class CognitoIdpBackend(BaseBackend):
return user_pool.users[username]
- def list_users(self, user_pool_id):
+ @paginate(60, "pagination_token", "limit")
+ def list_users(self, user_pool_id, pagination_token=None, limit=None):
user_pool = self.user_pools.get(user_pool_id)
if not user_pool:
raise ResourceNotFoundError(user_pool_id)
diff --git a/moto/cognitoidp/responses.py b/moto/cognitoidp/responses.py
index 50939786b..cb5374a6b 100644
--- a/moto/cognitoidp/responses.py
+++ b/moto/cognitoidp/responses.py
@@ -22,10 +22,17 @@ class CognitoIdpResponse(BaseResponse):
})
def list_user_pools(self):
- user_pools = cognitoidp_backends[self.region].list_user_pools()
- return json.dumps({
- "UserPools": [user_pool.to_json() for user_pool in user_pools]
- })
+ max_results = self._get_param("MaxResults")
+ next_token = self._get_param("NextToken", "0")
+ user_pools, next_token = cognitoidp_backends[self.region].list_user_pools(
+ max_results=max_results, next_token=next_token
+ )
+ response = {
+ "UserPools": [user_pool.to_json() for user_pool in user_pools],
+ }
+ if next_token:
+ response["NextToken"] = str(next_token)
+ return json.dumps(response)
def describe_user_pool(self):
user_pool_id = self._get_param("UserPoolId")
@@ -72,10 +79,16 @@ class CognitoIdpResponse(BaseResponse):
def list_user_pool_clients(self):
user_pool_id = self._get_param("UserPoolId")
- user_pool_clients = cognitoidp_backends[self.region].list_user_pool_clients(user_pool_id)
- return json.dumps({
+ max_results = self._get_param("MaxResults")
+ next_token = self._get_param("NextToken", "0")
+ user_pool_clients, next_token = cognitoidp_backends[self.region].list_user_pool_clients(user_pool_id,
+ max_results=max_results, next_token=next_token)
+ response = {
"UserPoolClients": [user_pool_client.to_json() for user_pool_client in user_pool_clients]
- })
+ }
+ if next_token:
+ response["NextToken"] = str(next_token)
+ return json.dumps(response)
def describe_user_pool_client(self):
user_pool_id = self._get_param("UserPoolId")
@@ -110,10 +123,17 @@ class CognitoIdpResponse(BaseResponse):
def list_identity_providers(self):
user_pool_id = self._get_param("UserPoolId")
- identity_providers = cognitoidp_backends[self.region].list_identity_providers(user_pool_id)
- return json.dumps({
+ max_results = self._get_param("MaxResults")
+ next_token = self._get_param("NextToken", "0")
+ identity_providers, next_token = cognitoidp_backends[self.region].list_identity_providers(
+ user_pool_id, max_results=max_results, next_token=next_token
+ )
+ response = {
"Providers": [identity_provider.to_json() for identity_provider in identity_providers]
- })
+ }
+ if next_token:
+ response["NextToken"] = str(next_token)
+ return json.dumps(response)
def describe_identity_provider(self):
user_pool_id = self._get_param("UserPoolId")
@@ -155,10 +175,15 @@ class CognitoIdpResponse(BaseResponse):
def list_users(self):
user_pool_id = self._get_param("UserPoolId")
- users = cognitoidp_backends[self.region].list_users(user_pool_id)
- return json.dumps({
- "Users": [user.to_json(extended=True) for user in users]
- })
+ limit = self._get_param("Limit")
+ token = self._get_param("PaginationToken")
+ users, token = cognitoidp_backends[self.region].list_users(user_pool_id,
+ limit=limit,
+ pagination_token=token)
+ response = {"Users": [user.to_json(extended=True) for user in users]}
+ if token:
+ response["PaginationToken"] = str(token)
+ return json.dumps(response)
def admin_disable_user(self):
user_pool_id = self._get_param("UserPoolId")
diff --git a/moto/core/models.py b/moto/core/models.py
index adc06a9c0..19267ca08 100644
--- a/moto/core/models.py
+++ b/moto/core/models.py
@@ -2,11 +2,14 @@
from __future__ import unicode_literals
from __future__ import absolute_import
-from collections import defaultdict
import functools
import inspect
import re
import six
+from io import BytesIO
+from collections import defaultdict
+from botocore.handlers import BUILTIN_HANDLERS
+from botocore.awsrequest import AWSResponse
from moto import settings
import responses
@@ -233,7 +236,111 @@ class ResponsesMockAWS(BaseMockAWS):
pass
-MockAWS = ResponsesMockAWS
+BOTOCORE_HTTP_METHODS = [
+ 'GET', 'DELETE', 'HEAD', 'OPTIONS', 'PATCH', 'POST', 'PUT'
+]
+
+
+class MockRawResponse(BytesIO):
+ def __init__(self, input):
+ if isinstance(input, six.text_type):
+ input = input.encode('utf-8')
+ super(MockRawResponse, self).__init__(input)
+
+ def stream(self, **kwargs):
+ contents = self.read()
+ while contents:
+ yield contents
+ contents = self.read()
+
+
+class BotocoreStubber(object):
+ def __init__(self):
+ self.enabled = False
+ self.methods = defaultdict(list)
+
+ def reset(self):
+ self.methods.clear()
+
+ def register_response(self, method, pattern, response):
+ matchers = self.methods[method]
+ matchers.append((pattern, response))
+
+ def __call__(self, event_name, request, **kwargs):
+ if not self.enabled:
+ return None
+
+ response = None
+ response_callback = None
+ found_index = None
+ matchers = self.methods.get(request.method)
+
+ base_url = request.url.split('?', 1)[0]
+ for i, (pattern, callback) in enumerate(matchers):
+ if pattern.match(base_url):
+ if found_index is None:
+ found_index = i
+ response_callback = callback
+ else:
+ matchers.pop(found_index)
+ break
+
+ if response_callback is not None:
+ for header, value in request.headers.items():
+ if isinstance(value, six.binary_type):
+ request.headers[header] = value.decode('utf-8')
+ status, headers, body = response_callback(request, request.url, request.headers)
+ body = MockRawResponse(body)
+ response = AWSResponse(request.url, status, headers, body)
+
+ return response
+
+
+botocore_stubber = BotocoreStubber()
+BUILTIN_HANDLERS.append(('before-send', botocore_stubber))
+
+
+class BotocoreEventMockAWS(BaseMockAWS):
+ def reset(self):
+ botocore_stubber.reset()
+ responses_mock.reset()
+
+ def enable_patching(self):
+ botocore_stubber.enabled = True
+ for method in BOTOCORE_HTTP_METHODS:
+ for backend in self.backends_for_urls.values():
+ for key, value in backend.urls.items():
+ pattern = re.compile(key)
+ botocore_stubber.register_response(method, pattern, value)
+
+ if not hasattr(responses_mock, '_patcher') or not hasattr(responses_mock._patcher, 'target'):
+ responses_mock.start()
+
+ for method in RESPONSES_METHODS:
+ # for backend in default_backends.values():
+ for backend in self.backends_for_urls.values():
+ for key, value in backend.urls.items():
+ responses_mock.add(
+ CallbackResponse(
+ method=method,
+ url=re.compile(key),
+ callback=convert_flask_to_responses_response(value),
+ stream=True,
+ match_querystring=False,
+ )
+ )
+
+ def disable_patching(self):
+ botocore_stubber.enabled = False
+ self.reset()
+
+ try:
+ responses_mock.stop()
+ except RuntimeError:
+ pass
+
+
+MockAWS = BotocoreEventMockAWS
class ServerModeMockAWS(BaseMockAWS):
diff --git a/moto/core/utils.py b/moto/core/utils.py
index 86e7632b0..777a03752 100644
--- a/moto/core/utils.py
+++ b/moto/core/utils.py
@@ -8,6 +8,7 @@ import random
import re
import six
import string
+from six.moves.urllib.parse import urlparse
REQUEST_ID_LONG = string.digits + string.ascii_uppercase
@@ -286,3 +287,13 @@ def amzn_request_id(f):
return status, headers, body
return _wrapper
+
+
+def path_url(url):
+ parsed_url = urlparse(url)
+ path = parsed_url.path
+ if not path:
+ path = '/'
+ if parsed_url.query:
+ path = path + '?' + parsed_url.query
+ return path
diff --git a/moto/ecs/exceptions.py b/moto/ecs/exceptions.py
index c23d6fd1d..bb7e685c8 100644
--- a/moto/ecs/exceptions.py
+++ b/moto/ecs/exceptions.py
@@ -8,4 +8,6 @@ class ServiceNotFoundException(RESTError):
def __init__(self, service_name):
super(ServiceNotFoundException, self).__init__(
error_type="ServiceNotFoundException",
- message="The service {0} does not exist".format(service_name))
+ message="The service {0} does not exist".format(service_name),
+ template='error_json',
+ )
diff --git a/moto/emr/responses.py b/moto/emr/responses.py
index 49e37ab9a..933e0177b 100644
--- a/moto/emr/responses.py
+++ b/moto/emr/responses.py
@@ -613,13 +613,11 @@ DESCRIBE_STEP_TEMPLATE = """
@@ -734,7 +766,7 @@ CREATE_INSTANCE_PROFILE_TEMPLATE = """
{{ policy }}
{% endfor %}
+ false
- false
7a62c49f-347e-4fc4-9331-6e8eEXAMPLE
@@ -1277,23 +1309,23 @@ LIST_INSTANCE_PROFILES_FOR_ROLE_TEMPLATE = """
{% for profile in instance_profiles %}
- {{ profile.id }}
-
- {% for role in profile.roles %}
-
- {{ role.path }}
- {{ role.arn }}
- {{ role.name }}
- {{ role.assume_policy_document }}
- 2012-05-09T15:45:35Z
- {{ role.id }}
-
- {% endfor %}
-
- {{ profile.name }}
- {{ profile.path }}
- {{ profile.arn }}
- 2012-05-09T16:27:11Z
+ {{ profile.id }}
+
+ {% for role in profile.roles %}
+
+ {{ role.path }}
+ {{ role.arn }}
+ {{ role.name }}
+ {{ role.assume_policy_document }}
+ {{ role.create_date }}
+ {{ role.id }}
+
+ {% endfor %}
+
+ {{ profile.name }}
+ {{ profile.path }}
+ {{ profile.arn }}
+ {{ profile.create_date }}
{% endfor %}
@@ -1382,7 +1414,7 @@ GET_ACCOUNT_AUTHORIZATION_DETAILS_TEMPLATE = """{{ user.path }}
{{ user.name }}
{{ user.arn }}
- 2012-05-09T15:45:35Z
+ {{ user.created_iso_8601 }}
{% endfor %}
@@ -1401,7 +1433,7 @@ GET_ACCOUNT_AUTHORIZATION_DETAILS_TEMPLATE = """{{ group.name }}
{{ group.path }}
{{ group.arn }}
- 2012-05-09T16:27:11Z
+ {{ group.create_date }}
{% endfor %}
@@ -1421,23 +1453,23 @@ GET_ACCOUNT_AUTHORIZATION_DETAILS_TEMPLATE = """
{% for profile in instance_profiles %}
- {{ profile.id }}
-
- {% for role in profile.roles %}
-
- {{ role.path }}
- {{ role.arn }}
- {{ role.name }}
- {{ role.assume_role_policy_document }}
- 2012-05-09T15:45:35Z
- {{ role.id }}
-
- {% endfor %}
-
- {{ profile.name }}
- {{ profile.path }}
- {{ profile.arn }}
- 2012-05-09T16:27:11Z
+ {{ profile.id }}
+
+ {% for role in profile.roles %}
+
+ {{ role.path }}
+ {{ role.arn }}
+ {{ role.name }}
+ {{ role.assume_role_policy_document }}
+ {{ role.create_date }}
+ {{ role.id }}
+
+ {% endfor %}
+
+ {{ profile.name }}
+ {{ profile.path }}
+ {{ profile.arn }}
+ {{ profile.create_date }}
{% endfor %}
@@ -1445,7 +1477,7 @@ GET_ACCOUNT_AUTHORIZATION_DETAILS_TEMPLATE = """{{ role.arn }}
{{ role.name }}
{{ role.assume_role_policy_document }}
- 2014-07-30T17:09:20Z
+ {{ role.create_date }}
{{ role.id }}
{% endfor %}
@@ -1474,9 +1506,9 @@ GET_ACCOUNT_AUTHORIZATION_DETAILS_TEMPLATE = """
{{ policy.arn }}
1
- 2012-05-09T16:27:11Z
+ {{ policy.create_datetime }}
true
- 2012-05-09T16:27:11Z
+ {{ policy.update_datetime }}
{% endfor %}
@@ -1485,3 +1517,53 @@ GET_ACCOUNT_AUTHORIZATION_DETAILS_TEMPLATE = """92e79ae7-7399-11e4-8c85-4b53eEXAMPLE
"""
+
+
+UPLOAD_SIGNING_CERTIFICATE_TEMPLATE = """
+
+
+ {{ cert.user_name }}
+ {{ cert.id }}
+ {{ cert.body }}
+ {{ cert.status }}
+
+
+
+ 7a62c49f-347e-4fc4-9331-6e8eEXAMPLE
+
+"""
+
+
+UPDATE_SIGNING_CERTIFICATE_TEMPLATE = """
+
+ EXAMPLE8-90ab-cdef-fedc-ba987EXAMPLE
+
+"""
+
+
+DELETE_SIGNING_CERTIFICATE_TEMPLATE = """
+
+ 7a62c49f-347e-4fc4-9331-6e8eEXAMPLE
+
+"""
+
+
+LIST_SIGNING_CERTIFICATES_TEMPLATE = """
+
+ {{ user_name }}
+
+ {% for cert in certificates %}
+
+ {{ user_name }}
+ {{ cert.id }}
+ {{ cert.body }}
+ {{ cert.status }}
+
+ {% endfor %}
+
+ false
+
+
+ 7a62c49f-347e-4fc4-9331-6e8eEXAMPLE
+
+"""
diff --git a/moto/iam/utils.py b/moto/iam/utils.py
index 1fae85a6c..f59bdfffe 100644
--- a/moto/iam/utils.py
+++ b/moto/iam/utils.py
@@ -12,8 +12,7 @@ def random_alphanumeric(length):
)
-def random_resource_id():
- size = 20
+def random_resource_id(size=20):
chars = list(range(10)) + list(string.ascii_lowercase)
return ''.join(six.text_type(random.choice(chars)) for x in range(size))
diff --git a/moto/s3/exceptions.py b/moto/s3/exceptions.py
index 26515dfd2..c7d82ddfd 100644
--- a/moto/s3/exceptions.py
+++ b/moto/s3/exceptions.py
@@ -178,3 +178,13 @@ class InvalidStorageClass(S3ClientError):
"InvalidStorageClass",
"The storage class you specified is not valid",
*args, **kwargs)
+
+
+class DuplicateTagKeys(S3ClientError):
+ code = 400
+
+ def __init__(self, *args, **kwargs):
+ super(DuplicateTagKeys, self).__init__(
+ "InvalidTag",
+ "Cannot provide multiple Tags with the same key",
+ *args, **kwargs)
diff --git a/moto/s3/models.py b/moto/s3/models.py
index bb4d7848c..fd53417fa 100644
--- a/moto/s3/models.py
+++ b/moto/s3/models.py
@@ -15,7 +15,7 @@ from bisect import insort
from moto.core import BaseBackend, BaseModel
from moto.core.utils import iso_8601_datetime_with_milliseconds, rfc_1123_datetime
from .exceptions import BucketAlreadyExists, MissingBucket, InvalidPart, EntityTooSmall, MissingKey, \
- InvalidNotificationDestination, MalformedXML, InvalidStorageClass
+ InvalidNotificationDestination, MalformedXML, InvalidStorageClass, DuplicateTagKeys
from .utils import clean_key_name, _VersionedKeyStore
UPLOAD_ID_BYTES = 43
@@ -773,6 +773,9 @@ class S3Backend(BaseBackend):
return key
def put_bucket_tagging(self, bucket_name, tagging):
+ tag_keys = [tag.key for tag in tagging.tag_set.tags]
+ if len(tag_keys) != len(set(tag_keys)):
+ raise DuplicateTagKeys()
bucket = self.get_bucket(bucket_name)
bucket.set_tags(tagging)
diff --git a/moto/s3/responses.py b/moto/s3/responses.py
index 962025cb1..13e5f87d9 100755
--- a/moto/s3/responses.py
+++ b/moto/s3/responses.py
@@ -10,6 +10,7 @@ import xmltodict
from moto.packages.httpretty.core import HTTPrettyRequest
from moto.core.responses import _TemplateEnvironmentMixin
+from moto.core.utils import path_url
from moto.s3bucket_path.utils import bucket_name_from_url as bucketpath_bucket_name_from_url, \
parse_key_name as bucketpath_parse_key_name, is_delete_keys as bucketpath_is_delete_keys
@@ -487,7 +488,7 @@ class ResponseObject(_TemplateEnvironmentMixin):
if isinstance(request, HTTPrettyRequest):
path = request.path
else:
- path = request.full_path if hasattr(request, 'full_path') else request.path_url
+ path = request.full_path if hasattr(request, 'full_path') else path_url(request.url)
if self.is_delete_keys(request, path, bucket_name):
return self._bucket_response_delete_keys(request, body, bucket_name, headers)
@@ -708,7 +709,10 @@ class ResponseObject(_TemplateEnvironmentMixin):
# Copy key
# you can have a quoted ?version=abc with a version Id, so work on
# we need to parse the unquoted string first
- src_key_parsed = urlparse(request.headers.get("x-amz-copy-source"))
+ src_key = request.headers.get("x-amz-copy-source")
+ if isinstance(src_key, six.binary_type):
+ src_key = src_key.decode('utf-8')
+ src_key_parsed = urlparse(src_key)
src_bucket, src_key = unquote(src_key_parsed.path).\
lstrip("/").split("/", 1)
src_version_id = parse_qs(src_key_parsed.query).get(
diff --git a/moto/secretsmanager/models.py b/moto/secretsmanager/models.py
index 1404a0ec8..1350ab469 100644
--- a/moto/secretsmanager/models.py
+++ b/moto/secretsmanager/models.py
@@ -2,6 +2,7 @@ from __future__ import unicode_literals
import time
import json
+import uuid
import boto3
@@ -18,10 +19,6 @@ class SecretsManager(BaseModel):
def __init__(self, region_name, **kwargs):
self.region = region_name
- self.secret_id = kwargs.get('secret_id', '')
- self.version_id = kwargs.get('version_id', '')
- self.version_stage = kwargs.get('version_stage', '')
- self.secret_string = ''
class SecretsManagerBackend(BaseBackend):
@@ -29,14 +26,7 @@ class SecretsManagerBackend(BaseBackend):
def __init__(self, region_name=None, **kwargs):
super(SecretsManagerBackend, self).__init__()
self.region = region_name
- self.secret_id = kwargs.get('secret_id', '')
- self.name = kwargs.get('name', '')
- self.createdate = int(time.time())
- self.secret_string = ''
- self.rotation_enabled = False
- self.rotation_lambda_arn = ''
- self.auto_rotate_after_days = 0
- self.version_id = ''
+ self.secrets = {}
def reset(self):
region_name = self.region
@@ -44,36 +34,50 @@ class SecretsManagerBackend(BaseBackend):
self.__init__(region_name)
def _is_valid_identifier(self, identifier):
- return identifier in (self.name, self.secret_id)
+ return identifier in self.secrets
def get_secret_value(self, secret_id, version_id, version_stage):
if not self._is_valid_identifier(secret_id):
raise ResourceNotFoundException()
+ secret = self.secrets[secret_id]
+
response = json.dumps({
- "ARN": secret_arn(self.region, self.secret_id),
- "Name": self.name,
- "VersionId": "A435958A-D821-4193-B719-B7769357AER4",
- "SecretString": self.secret_string,
+ "ARN": secret_arn(self.region, secret['secret_id']),
+ "Name": secret['name'],
+ "VersionId": secret['version_id'],
+ "SecretString": secret['secret_string'],
"VersionStages": [
"AWSCURRENT",
],
- "CreatedDate": "2018-05-23 13:16:57.198000"
+ "CreatedDate": secret['createdate']
})
return response
- def create_secret(self, name, secret_string, **kwargs):
+ def create_secret(self, name, secret_string, tags, **kwargs):
- self.secret_string = secret_string
- self.secret_id = name
- self.name = name
+ generated_version_id = str(uuid.uuid4())
+
+ secret = {
+ 'secret_string': secret_string,
+ 'secret_id': name,
+ 'name': name,
+ 'createdate': int(time.time()),
+ 'rotation_enabled': False,
+ 'rotation_lambda_arn': '',
+ 'auto_rotate_after_days': 0,
+ 'version_id': generated_version_id,
+ 'tags': tags
+ }
+
+ self.secrets[name] = secret
response = json.dumps({
"ARN": secret_arn(self.region, name),
- "Name": self.name,
- "VersionId": "A435958A-D821-4193-B719-B7769357AER4",
+ "Name": name,
+ "VersionId": generated_version_id,
})
return response
@@ -82,26 +86,23 @@ class SecretsManagerBackend(BaseBackend):
if not self._is_valid_identifier(secret_id):
raise ResourceNotFoundException
+ secret = self.secrets[secret_id]
+
response = json.dumps({
- "ARN": secret_arn(self.region, self.secret_id),
- "Name": self.name,
+ "ARN": secret_arn(self.region, secret['secret_id']),
+ "Name": secret['name'],
"Description": "",
"KmsKeyId": "",
- "RotationEnabled": self.rotation_enabled,
- "RotationLambdaARN": self.rotation_lambda_arn,
+ "RotationEnabled": secret['rotation_enabled'],
+ "RotationLambdaARN": secret['rotation_lambda_arn'],
"RotationRules": {
- "AutomaticallyAfterDays": self.auto_rotate_after_days
+ "AutomaticallyAfterDays": secret['auto_rotate_after_days']
},
"LastRotatedDate": None,
"LastChangedDate": None,
"LastAccessedDate": None,
"DeletedDate": None,
- "Tags": [
- {
- "Key": "",
- "Value": ""
- },
- ]
+ "Tags": secret['tags']
})
return response
@@ -141,17 +142,19 @@ class SecretsManagerBackend(BaseBackend):
)
raise InvalidParameterException(msg)
- self.version_id = client_request_token or ''
- self.rotation_lambda_arn = rotation_lambda_arn or ''
+ secret = self.secrets[secret_id]
+
+ secret['version_id'] = client_request_token or ''
+ secret['rotation_lambda_arn'] = rotation_lambda_arn or ''
if rotation_rules:
- self.auto_rotate_after_days = rotation_rules.get(rotation_days, 0)
- if self.auto_rotate_after_days > 0:
- self.rotation_enabled = True
+ secret['auto_rotate_after_days'] = rotation_rules.get(rotation_days, 0)
+ if secret['auto_rotate_after_days'] > 0:
+ secret['rotation_enabled'] = True
response = json.dumps({
- "ARN": secret_arn(self.region, self.secret_id),
- "Name": self.name,
- "VersionId": self.version_id
+ "ARN": secret_arn(self.region, secret['secret_id']),
+ "Name": secret['name'],
+ "VersionId": secret['version_id']
})
return response
diff --git a/moto/secretsmanager/responses.py b/moto/secretsmanager/responses.py
index b8b6872a8..932e7bfd7 100644
--- a/moto/secretsmanager/responses.py
+++ b/moto/secretsmanager/responses.py
@@ -19,9 +19,11 @@ class SecretsManagerResponse(BaseResponse):
def create_secret(self):
name = self._get_param('Name')
secret_string = self._get_param('SecretString')
+ tags = self._get_param('Tags', if_none=[])
return secretsmanager_backends[self.region].create_secret(
name=name,
- secret_string=secret_string
+ secret_string=secret_string,
+ tags=tags
)
def get_random_password(self):
diff --git a/moto/secretsmanager/utils.py b/moto/secretsmanager/utils.py
index 2cb92020a..231fea296 100644
--- a/moto/secretsmanager/utils.py
+++ b/moto/secretsmanager/utils.py
@@ -52,8 +52,9 @@ def random_password(password_length, exclude_characters, exclude_numbers,
def secret_arn(region, secret_id):
- return "arn:aws:secretsmanager:{0}:1234567890:secret:{1}-rIjad".format(
- region, secret_id)
+ id_string = ''.join(random.choice(string.ascii_letters) for _ in range(5))
+ return "arn:aws:secretsmanager:{0}:1234567890:secret:{1}-{2}".format(
+ region, secret_id, id_string)
def _exclude_characters(password, exclude_characters):
diff --git a/requirements-dev.txt b/requirements-dev.txt
index 111cd5f3f..f87ab3db6 100644
--- a/requirements-dev.txt
+++ b/requirements-dev.txt
@@ -8,7 +8,7 @@ freezegun
flask
boto>=2.45.0
boto3>=1.4.4
-botocore>=1.8.36
+botocore>=1.12.13
six>=1.9
prompt-toolkit==1.0.14
click==6.7
diff --git a/setup.py b/setup.py
index 34002b79d..fbb3cb376 100755
--- a/setup.py
+++ b/setup.py
@@ -1,15 +1,28 @@
#!/usr/bin/env python
from __future__ import unicode_literals
+import codecs
+import os
+import re
import setuptools
from setuptools import setup, find_packages
import sys
+# Borrowed from pip at https://github.com/pypa/pip/blob/62c27dee45625e1b63d1e023b0656310f276e050/setup.py#L11-L15
+here = os.path.abspath(os.path.dirname(__file__))
+
+def read(*parts):
+ # intentionally *not* adding an encoding option to open, See:
+ # https://github.com/pypa/virtualenv/issues/201#issuecomment-3145690
+ with codecs.open(os.path.join(here, *parts), 'r') as fp:
+ return fp.read()
+
+
install_requires = [
"Jinja2>=2.7.3",
"boto>=2.36.0",
- "boto3>=1.6.16,<1.8",
- "botocore>=1.9.16,<1.11",
+ "boto3>=1.6.16",
+ "botocore>=1.12.13",
"cryptography>=2.3.0",
"requests>=2.5",
"xmltodict",
@@ -22,7 +35,7 @@ install_requires = [
"mock",
"docker>=2.5.1",
"jsondiff==1.1.2",
- "aws-xray-sdk<0.96,>=0.93",
+ "aws-xray-sdk!=0.96,>=0.93",
"responses>=0.9.0",
]
@@ -40,9 +53,11 @@ else:
setup(
name='moto',
- version='1.3.6',
+ version='1.3.7',
description='A library that allows your python tests to easily'
' mock out the boto library',
+ long_description=read('README.md'),
+ long_description_content_type='text/markdown',
author='Steve Pulec',
author_email='spulec@gmail.com',
url='https://github.com/spulec/moto',
diff --git a/tests/test_cognitoidp/test_cognitoidp.py b/tests/test_cognitoidp/test_cognitoidp.py
index f72a44762..362740ce3 100644
--- a/tests/test_cognitoidp/test_cognitoidp.py
+++ b/tests/test_cognitoidp/test_cognitoidp.py
@@ -41,6 +41,56 @@ def test_list_user_pools():
result["UserPools"][0]["Name"].should.equal(name)
+@mock_cognitoidp
+def test_list_user_pools_returns_max_items():
+ conn = boto3.client("cognito-idp", "us-west-2")
+
+ # Given 10 user pools
+ pool_count = 10
+ for i in range(pool_count):
+ conn.create_user_pool(PoolName=str(uuid.uuid4()))
+
+ max_results = 5
+ result = conn.list_user_pools(MaxResults=max_results)
+ result["UserPools"].should.have.length_of(max_results)
+ result.should.have.key("NextToken")
+
+
+@mock_cognitoidp
+def test_list_user_pools_returns_next_tokens():
+ conn = boto3.client("cognito-idp", "us-west-2")
+
+ # Given 10 user pool clients
+ pool_count = 10
+ for i in range(pool_count):
+ conn.create_user_pool(PoolName=str(uuid.uuid4()))
+
+ max_results = 5
+ result = conn.list_user_pools(MaxResults=max_results)
+ result["UserPools"].should.have.length_of(max_results)
+ result.should.have.key("NextToken")
+
+ next_token = result["NextToken"]
+ result_2 = conn.list_user_pools(MaxResults=max_results, NextToken=next_token)
+ result_2["UserPools"].should.have.length_of(max_results)
+ result_2.shouldnt.have.key("NextToken")
+
+
+@mock_cognitoidp
+def test_list_user_pools_when_max_items_more_than_total_items():
+ conn = boto3.client("cognito-idp", "us-west-2")
+
+ # Given 10 user pool clients
+ pool_count = 10
+ for i in range(pool_count):
+ conn.create_user_pool(PoolName=str(uuid.uuid4()))
+
+ max_results = pool_count + 5
+ result = conn.list_user_pools(MaxResults=max_results)
+ result["UserPools"].should.have.length_of(pool_count)
+ result.shouldnt.have.key("NextToken")
+
+
@mock_cognitoidp
def test_describe_user_pool():
conn = boto3.client("cognito-idp", "us-west-2")
@@ -140,6 +190,67 @@ def test_list_user_pool_clients():
result["UserPoolClients"][0]["ClientName"].should.equal(client_name)
+@mock_cognitoidp
+def test_list_user_pool_clients_returns_max_items():
+ conn = boto3.client("cognito-idp", "us-west-2")
+ user_pool_id = conn.create_user_pool(PoolName=str(uuid.uuid4()))["UserPool"]["Id"]
+
+ # Given 10 user pool clients
+ client_count = 10
+ for i in range(client_count):
+ client_name = str(uuid.uuid4())
+ conn.create_user_pool_client(UserPoolId=user_pool_id,
+ ClientName=client_name)
+ max_results = 5
+ result = conn.list_user_pool_clients(UserPoolId=user_pool_id,
+ MaxResults=max_results)
+ result["UserPoolClients"].should.have.length_of(max_results)
+ result.should.have.key("NextToken")
+
+
+@mock_cognitoidp
+def test_list_user_pool_clients_returns_next_tokens():
+ conn = boto3.client("cognito-idp", "us-west-2")
+ user_pool_id = conn.create_user_pool(PoolName=str(uuid.uuid4()))["UserPool"]["Id"]
+
+ # Given 10 user pool clients
+ client_count = 10
+ for i in range(client_count):
+ client_name = str(uuid.uuid4())
+ conn.create_user_pool_client(UserPoolId=user_pool_id,
+ ClientName=client_name)
+ max_results = 5
+ result = conn.list_user_pool_clients(UserPoolId=user_pool_id,
+ MaxResults=max_results)
+ result["UserPoolClients"].should.have.length_of(max_results)
+ result.should.have.key("NextToken")
+
+ next_token = result["NextToken"]
+ result_2 = conn.list_user_pool_clients(UserPoolId=user_pool_id,
+ MaxResults=max_results,
+ NextToken=next_token)
+ result_2["UserPoolClients"].should.have.length_of(max_results)
+ result_2.shouldnt.have.key("NextToken")
+
+
+@mock_cognitoidp
+def test_list_user_pool_clients_when_max_items_more_than_total_items():
+ conn = boto3.client("cognito-idp", "us-west-2")
+ user_pool_id = conn.create_user_pool(PoolName=str(uuid.uuid4()))["UserPool"]["Id"]
+
+ # Given 10 user pool clients
+ client_count = 10
+ for i in range(client_count):
+ client_name = str(uuid.uuid4())
+ conn.create_user_pool_client(UserPoolId=user_pool_id,
+ ClientName=client_name)
+ max_results = client_count + 5
+ result = conn.list_user_pool_clients(UserPoolId=user_pool_id,
+ MaxResults=max_results)
+ result["UserPoolClients"].should.have.length_of(client_count)
+ result.shouldnt.have.key("NextToken")
+
+
@mock_cognitoidp
def test_describe_user_pool_client():
conn = boto3.client("cognito-idp", "us-west-2")
@@ -264,6 +375,83 @@ def test_list_identity_providers():
result["Providers"][0]["ProviderType"].should.equal(provider_type)
+@mock_cognitoidp
+def test_list_identity_providers_returns_max_items():
+ conn = boto3.client("cognito-idp", "us-west-2")
+ user_pool_id = conn.create_user_pool(PoolName=str(uuid.uuid4()))["UserPool"]["Id"]
+
+ # Given 10 identity providers linked to a user pool
+ identity_provider_count = 10
+ for i in range(identity_provider_count):
+ provider_name = str(uuid.uuid4())
+ provider_type = "Facebook"
+ conn.create_identity_provider(
+ UserPoolId=user_pool_id,
+ ProviderName=provider_name,
+ ProviderType=provider_type,
+ ProviderDetails={},
+ )
+
+ max_results = 5
+ result = conn.list_identity_providers(UserPoolId=user_pool_id,
+ MaxResults=max_results)
+ result["Providers"].should.have.length_of(max_results)
+ result.should.have.key("NextToken")
+
+
+@mock_cognitoidp
+def test_list_identity_providers_returns_next_tokens():
+ conn = boto3.client("cognito-idp", "us-west-2")
+ user_pool_id = conn.create_user_pool(PoolName=str(uuid.uuid4()))["UserPool"]["Id"]
+
+ # Given 10 identity providers linked to a user pool
+ identity_provider_count = 10
+ for i in range(identity_provider_count):
+ provider_name = str(uuid.uuid4())
+ provider_type = "Facebook"
+ conn.create_identity_provider(
+ UserPoolId=user_pool_id,
+ ProviderName=provider_name,
+ ProviderType=provider_type,
+ ProviderDetails={},
+ )
+
+ max_results = 5
+ result = conn.list_identity_providers(UserPoolId=user_pool_id, MaxResults=max_results)
+ result["Providers"].should.have.length_of(max_results)
+ result.should.have.key("NextToken")
+
+ next_token = result["NextToken"]
+ result_2 = conn.list_identity_providers(UserPoolId=user_pool_id,
+ MaxResults=max_results,
+ NextToken=next_token)
+ result_2["Providers"].should.have.length_of(max_results)
+ result_2.shouldnt.have.key("NextToken")
+
+
+@mock_cognitoidp
+def test_list_identity_providers_when_max_items_more_than_total_items():
+ conn = boto3.client("cognito-idp", "us-west-2")
+ user_pool_id = conn.create_user_pool(PoolName=str(uuid.uuid4()))["UserPool"]["Id"]
+
+ # Given 10 identity providers linked to a user pool
+ identity_provider_count = 10
+ for i in range(identity_provider_count):
+ provider_name = str(uuid.uuid4())
+ provider_type = "Facebook"
+ conn.create_identity_provider(
+ UserPoolId=user_pool_id,
+ ProviderName=provider_name,
+ ProviderType=provider_type,
+ ProviderDetails={},
+ )
+
+ max_results = identity_provider_count + 5
+ result = conn.list_identity_providers(UserPoolId=user_pool_id, MaxResults=max_results)
+ result["Providers"].should.have.length_of(identity_provider_count)
+ result.shouldnt.have.key("NextToken")
+
+
@mock_cognitoidp
def test_describe_identity_providers():
conn = boto3.client("cognito-idp", "us-west-2")
@@ -396,6 +584,62 @@ def test_list_users():
result["Users"][0]["Username"].should.equal(username)
+@mock_cognitoidp
+def test_list_users_returns_limit_items():
+ conn = boto3.client("cognito-idp", "us-west-2")
+ user_pool_id = conn.create_user_pool(PoolName=str(uuid.uuid4()))["UserPool"]["Id"]
+
+ # Given 10 users
+ user_count = 10
+ for i in range(user_count):
+ conn.admin_create_user(UserPoolId=user_pool_id,
+ Username=str(uuid.uuid4()))
+ max_results = 5
+ result = conn.list_users(UserPoolId=user_pool_id, Limit=max_results)
+ result["Users"].should.have.length_of(max_results)
+ result.should.have.key("PaginationToken")
+
+
+@mock_cognitoidp
+def test_list_users_returns_pagination_tokens():
+ conn = boto3.client("cognito-idp", "us-west-2")
+ user_pool_id = conn.create_user_pool(PoolName=str(uuid.uuid4()))["UserPool"]["Id"]
+
+ # Given 10 users
+ user_count = 10
+ for i in range(user_count):
+ conn.admin_create_user(UserPoolId=user_pool_id,
+ Username=str(uuid.uuid4()))
+
+ max_results = 5
+ result = conn.list_users(UserPoolId=user_pool_id, Limit=max_results)
+ result["Users"].should.have.length_of(max_results)
+ result.should.have.key("PaginationToken")
+
+ next_token = result["PaginationToken"]
+ result_2 = conn.list_users(UserPoolId=user_pool_id,
+ Limit=max_results, PaginationToken=next_token)
+ result_2["Users"].should.have.length_of(max_results)
+ result_2.shouldnt.have.key("PaginationToken")
+
+
+@mock_cognitoidp
+def test_list_users_when_limit_more_than_total_items():
+ conn = boto3.client("cognito-idp", "us-west-2")
+ user_pool_id = conn.create_user_pool(PoolName=str(uuid.uuid4()))["UserPool"]["Id"]
+
+ # Given 10 users
+ user_count = 10
+ for i in range(user_count):
+ conn.admin_create_user(UserPoolId=user_pool_id,
+ Username=str(uuid.uuid4()))
+
+ max_results = user_count + 5
+ result = conn.list_users(UserPoolId=user_pool_id, Limit=max_results)
+ result["Users"].should.have.length_of(user_count)
+ result.shouldnt.have.key("PaginationToken")
+
+
@mock_cognitoidp
def test_admin_disable_user():
conn = boto3.client("cognito-idp", "us-west-2")
diff --git a/tests/test_ecs/test_ecs_boto3.py b/tests/test_ecs/test_ecs_boto3.py
index 70c1463ee..a0e8318da 100644
--- a/tests/test_ecs/test_ecs_boto3.py
+++ b/tests/test_ecs/test_ecs_boto3.py
@@ -631,7 +631,22 @@ def test_delete_service():
response['service']['schedulingStrategy'].should.equal('REPLICA')
response['service']['taskDefinition'].should.equal(
'arn:aws:ecs:us-east-1:012345678910:task-definition/test_ecs_task:1')
-
+
+
+@mock_ecs
+def test_update_non_existant_service():
+ client = boto3.client('ecs', region_name='us-east-1')
+ try:
+ client.update_service(
+ cluster="my-clustet",
+ service="my-service",
+ desiredCount=0,
+ )
+ except ClientError as exc:
+ error_code = exc.response['Error']['Code']
+ error_code.should.equal('ServiceNotFoundException')
+ else:
+ raise Exception("Didn't raise ClientError")
@mock_ec2
diff --git a/tests/test_iam/test_iam.py b/tests/test_iam/test_iam.py
index bc23ff712..7bf38f48e 100644
--- a/tests/test_iam/test_iam.py
+++ b/tests/test_iam/test_iam.py
@@ -14,6 +14,19 @@ from nose.tools import raises
from tests.helpers import requires_boto_gte
+MOCK_CERT = """-----BEGIN CERTIFICATE-----
+MIIBpzCCARACCQCY5yOdxCTrGjANBgkqhkiG9w0BAQsFADAXMRUwEwYDVQQKDAxt
+b3RvIHRlc3RpbmcwIBcNMTgxMTA1MTkwNTIwWhgPMjI5MjA4MTkxOTA1MjBaMBcx
+FTATBgNVBAoMDG1vdG8gdGVzdGluZzCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkC
+gYEA1Jn3g2h7LD3FLqdpcYNbFXCS4V4eDpuTCje9vKFcC3pi/01147X3zdfPy8Mt
+ZhKxcREOwm4NXykh23P9KW7fBovpNwnbYsbPqj8Hf1ZaClrgku1arTVhEnKjx8zO
+vaR/bVLCss4uE0E0VM1tJn/QGQsfthFsjuHtwx8uIWz35tUCAwEAATANBgkqhkiG
+9w0BAQsFAAOBgQBWdOQ7bDc2nWkUhFjZoNIZrqjyNdjlMUndpwREVD7FQ/DuxJMj
+FyDHrtlrS80dPUQWNYHw++oACDpWO01LGLPPrGmuO/7cOdojPEd852q5gd+7W9xt
+8vUH+pBa6IBLbvBp+szli51V3TLSWcoyy4ceJNQU2vCkTLoFdS0RLd/7tQ==
+-----END CERTIFICATE-----"""
+
+
@mock_iam_deprecated()
def test_get_all_server_certs():
conn = boto.connect_iam()
@@ -108,6 +121,10 @@ def test_create_role_and_instance_profile():
conn.list_roles().roles[0].role_name.should.equal('my-role')
+ # Test with an empty path:
+ profile = conn.create_instance_profile('my-other-profile')
+ profile.path.should.equal('/')
+
@mock_iam_deprecated()
def test_remove_role_from_instance_profile():
@@ -700,10 +717,10 @@ def test_get_account_authorization_details():
import json
conn = boto3.client('iam', region_name='us-east-1')
conn.create_role(RoleName="my-role", AssumeRolePolicyDocument="some policy", Path="/my-path/")
- conn.create_user(Path='/', UserName='testCloudAuxUser')
- conn.create_group(Path='/', GroupName='testCloudAuxGroup')
+ conn.create_user(Path='/', UserName='testUser')
+ conn.create_group(Path='/', GroupName='testGroup')
conn.create_policy(
- PolicyName='testCloudAuxPolicy',
+ PolicyName='testPolicy',
Path='/',
PolicyDocument=json.dumps({
"Version": "2012-10-17",
@@ -715,46 +732,110 @@ def test_get_account_authorization_details():
}
]
}),
- Description='Test CloudAux Policy'
+ Description='Test Policy'
)
+ conn.create_instance_profile(InstanceProfileName='ipn')
+ conn.add_role_to_instance_profile(InstanceProfileName='ipn', RoleName='my-role')
+
result = conn.get_account_authorization_details(Filter=['Role'])
- len(result['RoleDetailList']) == 1
- len(result['UserDetailList']) == 0
- len(result['GroupDetailList']) == 0
- len(result['Policies']) == 0
+ assert len(result['RoleDetailList']) == 1
+ assert len(result['UserDetailList']) == 0
+ assert len(result['GroupDetailList']) == 0
+ assert len(result['Policies']) == 0
+ assert len(result['RoleDetailList'][0]['InstanceProfileList']) == 1
result = conn.get_account_authorization_details(Filter=['User'])
- len(result['RoleDetailList']) == 0
- len(result['UserDetailList']) == 1
- len(result['GroupDetailList']) == 0
- len(result['Policies']) == 0
+ assert len(result['RoleDetailList']) == 0
+ assert len(result['UserDetailList']) == 1
+ assert len(result['GroupDetailList']) == 0
+ assert len(result['Policies']) == 0
result = conn.get_account_authorization_details(Filter=['Group'])
- len(result['RoleDetailList']) == 0
- len(result['UserDetailList']) == 0
- len(result['GroupDetailList']) == 1
- len(result['Policies']) == 0
+ assert len(result['RoleDetailList']) == 0
+ assert len(result['UserDetailList']) == 0
+ assert len(result['GroupDetailList']) == 1
+ assert len(result['Policies']) == 0
result = conn.get_account_authorization_details(Filter=['LocalManagedPolicy'])
- len(result['RoleDetailList']) == 0
- len(result['UserDetailList']) == 0
- len(result['GroupDetailList']) == 0
- len(result['Policies']) == 1
+ assert len(result['RoleDetailList']) == 0
+ assert len(result['UserDetailList']) == 0
+ assert len(result['GroupDetailList']) == 0
+ assert len(result['Policies']) == 1
# Check for greater than 1 since this should always be greater than one but might change.
# See iam/aws_managed_policies.py
result = conn.get_account_authorization_details(Filter=['AWSManagedPolicy'])
- len(result['RoleDetailList']) == 0
- len(result['UserDetailList']) == 0
- len(result['GroupDetailList']) == 0
- len(result['Policies']) > 1
+ assert len(result['RoleDetailList']) == 0
+ assert len(result['UserDetailList']) == 0
+ assert len(result['GroupDetailList']) == 0
+ assert len(result['Policies']) > 1
result = conn.get_account_authorization_details()
- len(result['RoleDetailList']) == 1
- len(result['UserDetailList']) == 1
- len(result['GroupDetailList']) == 1
- len(result['Policies']) > 1
+ assert len(result['RoleDetailList']) == 1
+ assert len(result['UserDetailList']) == 1
+ assert len(result['GroupDetailList']) == 1
+ assert len(result['Policies']) > 1
+@mock_iam
+def test_signing_certs():
+ client = boto3.client('iam', region_name='us-east-1')
+ # Create the IAM user first:
+ client.create_user(UserName='testing')
+
+ # Upload the cert:
+ resp = client.upload_signing_certificate(UserName='testing', CertificateBody=MOCK_CERT)['Certificate']
+ cert_id = resp['CertificateId']
+
+ assert resp['UserName'] == 'testing'
+ assert resp['Status'] == 'Active'
+ assert resp['CertificateBody'] == MOCK_CERT
+ assert resp['CertificateId']
+
+ # Upload a the cert with an invalid body:
+ with assert_raises(ClientError) as ce:
+ client.upload_signing_certificate(UserName='testing', CertificateBody='notacert')
+ assert ce.exception.response['Error']['Code'] == 'MalformedCertificate'
+
+ # Upload with an invalid user:
+ with assert_raises(ClientError):
+ client.upload_signing_certificate(UserName='notauser', CertificateBody=MOCK_CERT)
+
+ # Update:
+ client.update_signing_certificate(UserName='testing', CertificateId=cert_id, Status='Inactive')
+
+ with assert_raises(ClientError):
+ client.update_signing_certificate(UserName='notauser', CertificateId=cert_id, Status='Inactive')
+
+ with assert_raises(ClientError) as ce:
+ client.update_signing_certificate(UserName='testing', CertificateId='x' * 32, Status='Inactive')
+
+ assert ce.exception.response['Error']['Message'] == 'The Certificate with id {id} cannot be found.'.format(
+ id='x' * 32)
+
+ # List the certs:
+ resp = client.list_signing_certificates(UserName='testing')['Certificates']
+ assert len(resp) == 1
+ assert resp[0]['CertificateBody'] == MOCK_CERT
+ assert resp[0]['Status'] == 'Inactive' # Changed with the update call above.
+
+ with assert_raises(ClientError):
+ client.list_signing_certificates(UserName='notauser')
+
+ # Delete:
+ client.delete_signing_certificate(UserName='testing', CertificateId=cert_id)
+
+ with assert_raises(ClientError):
+ client.delete_signing_certificate(UserName='notauser', CertificateId=cert_id)
+
+ with assert_raises(ClientError) as ce:
+ client.delete_signing_certificate(UserName='testing', CertificateId=cert_id)
+
+ assert ce.exception.response['Error']['Message'] == 'The Certificate with id {id} cannot be found.'.format(
+ id=cert_id)
+
+ # Verify that it's not in the list:
+ resp = client.list_signing_certificates(UserName='testing')
+ assert not resp['Certificates']
diff --git a/tests/test_s3/test_s3.py b/tests/test_s3/test_s3.py
index 6e339abb6..ffafc0dfd 100644
--- a/tests/test_s3/test_s3.py
+++ b/tests/test_s3/test_s3.py
@@ -1553,6 +1553,24 @@ def test_boto3_put_bucket_tagging():
})
resp['ResponseMetadata']['HTTPStatusCode'].should.equal(200)
+ # With duplicate tag keys:
+ with assert_raises(ClientError) as err:
+ resp = s3.put_bucket_tagging(Bucket=bucket_name,
+ Tagging={
+ "TagSet": [
+ {
+ "Key": "TagOne",
+ "Value": "ValueOne"
+ },
+ {
+ "Key": "TagOne",
+ "Value": "ValueOneAgain"
+ }
+ ]
+ })
+ e = err.exception
+ e.response["Error"]["Code"].should.equal("InvalidTag")
+ e.response["Error"]["Message"].should.equal("Cannot provide multiple Tags with the same key")
@mock_s3
def test_boto3_get_bucket_tagging():
diff --git a/tests/test_secretsmanager/test_secretsmanager.py b/tests/test_secretsmanager/test_secretsmanager.py
index ec384a660..169282421 100644
--- a/tests/test_secretsmanager/test_secretsmanager.py
+++ b/tests/test_secretsmanager/test_secretsmanager.py
@@ -39,12 +39,28 @@ def test_create_secret():
conn = boto3.client('secretsmanager', region_name='us-east-1')
result = conn.create_secret(Name='test-secret', SecretString="foosecret")
- assert result['ARN'] == (
- 'arn:aws:secretsmanager:us-east-1:1234567890:secret:test-secret-rIjad')
+ assert result['ARN']
assert result['Name'] == 'test-secret'
secret = conn.get_secret_value(SecretId='test-secret')
assert secret['SecretString'] == 'foosecret'
+@mock_secretsmanager
+def test_create_secret_with_tags():
+ conn = boto3.client('secretsmanager', region_name='us-east-1')
+ secret_name = 'test-secret-with-tags'
+
+ result = conn.create_secret(
+ Name=secret_name,
+ SecretString="foosecret",
+ Tags=[{"Key": "Foo", "Value": "Bar"}, {"Key": "Mykey", "Value": "Myvalue"}]
+ )
+ assert result['ARN']
+ assert result['Name'] == secret_name
+ secret_value = conn.get_secret_value(SecretId=secret_name)
+ assert secret_value['SecretString'] == 'foosecret'
+ secret_details = conn.describe_secret(SecretId=secret_name)
+ assert secret_details['Tags'] == [{"Key": "Foo", "Value": "Bar"}, {"Key": "Mykey", "Value": "Myvalue"}]
+
@mock_secretsmanager
def test_get_random_password_default_length():
conn = boto3.client('secretsmanager', region_name='us-west-2')
@@ -159,10 +175,17 @@ def test_describe_secret():
conn.create_secret(Name='test-secret',
SecretString='foosecret')
+ conn.create_secret(Name='test-secret-2',
+ SecretString='barsecret')
+
secret_description = conn.describe_secret(SecretId='test-secret')
+ secret_description_2 = conn.describe_secret(SecretId='test-secret-2')
+
assert secret_description # Returned dict is not empty
- assert secret_description['ARN'] == (
- 'arn:aws:secretsmanager:us-west-2:1234567890:secret:test-secret-rIjad')
+ assert secret_description['Name'] == ('test-secret')
+ assert secret_description['ARN'] != '' # Test arn not empty
+ assert secret_description_2['Name'] == ('test-secret-2')
+ assert secret_description_2['ARN'] != '' # Test arn not empty
@mock_secretsmanager
def test_describe_secret_that_does_not_exist():
@@ -190,9 +213,7 @@ def test_rotate_secret():
rotated_secret = conn.rotate_secret(SecretId=secret_name)
assert rotated_secret
- assert rotated_secret['ARN'] == (
- 'arn:aws:secretsmanager:us-west-2:1234567890:secret:test-secret-rIjad'
- )
+ assert rotated_secret['ARN'] != '' # Test arn not empty
assert rotated_secret['Name'] == secret_name
assert rotated_secret['VersionId'] != ''
diff --git a/tests/test_secretsmanager/test_server.py b/tests/test_secretsmanager/test_server.py
index e573f9b67..d0f495f57 100644
--- a/tests/test_secretsmanager/test_server.py
+++ b/tests/test_secretsmanager/test_server.py
@@ -82,11 +82,20 @@ def test_create_secret():
headers={
"X-Amz-Target": "secretsmanager.CreateSecret"},
)
+ res_2 = test_client.post('/',
+ data={"Name": "test-secret-2",
+ "SecretString": "bar-secret"},
+ headers={
+ "X-Amz-Target": "secretsmanager.CreateSecret"},
+ )
json_data = json.loads(res.data.decode("utf-8"))
- assert json_data['ARN'] == (
- 'arn:aws:secretsmanager:us-east-1:1234567890:secret:test-secret-rIjad')
+ assert json_data['ARN'] != ''
assert json_data['Name'] == 'test-secret'
+
+ json_data_2 = json.loads(res_2.data.decode("utf-8"))
+ assert json_data_2['ARN'] != ''
+ assert json_data_2['Name'] == 'test-secret-2'
@mock_secretsmanager
def test_describe_secret():
@@ -107,12 +116,30 @@ def test_describe_secret():
"X-Amz-Target": "secretsmanager.DescribeSecret"
},
)
+
+ create_secret_2 = test_client.post('/',
+ data={"Name": "test-secret-2",
+ "SecretString": "barsecret"},
+ headers={
+ "X-Amz-Target": "secretsmanager.CreateSecret"
+ },
+ )
+ describe_secret_2 = test_client.post('/',
+ data={"SecretId": "test-secret-2"},
+ headers={
+ "X-Amz-Target": "secretsmanager.DescribeSecret"
+ },
+ )
json_data = json.loads(describe_secret.data.decode("utf-8"))
assert json_data # Returned dict is not empty
- assert json_data['ARN'] == (
- 'arn:aws:secretsmanager:us-east-1:1234567890:secret:test-secret-rIjad'
- )
+ assert json_data['ARN'] != ''
+ assert json_data['Name'] == 'test-secret'
+
+ json_data_2 = json.loads(describe_secret_2.data.decode("utf-8"))
+ assert json_data_2 # Returned dict is not empty
+ assert json_data_2['ARN'] != ''
+ assert json_data_2['Name'] == 'test-secret-2'
@mock_secretsmanager
def test_describe_secret_that_does_not_exist():
@@ -179,9 +206,7 @@ def test_rotate_secret():
json_data = json.loads(rotate_secret.data.decode("utf-8"))
assert json_data # Returned dict is not empty
- assert json_data['ARN'] == (
- 'arn:aws:secretsmanager:us-east-1:1234567890:secret:test-secret-rIjad'
- )
+ assert json_data['ARN'] != ''
assert json_data['Name'] == 'test-secret'
assert json_data['VersionId'] == client_request_token