Merge branch 'master' into jsondiff-1.1.2
This commit is contained in:
commit
0c5010989a
2
.gitignore
vendored
2
.gitignore
vendored
@ -15,4 +15,4 @@ python_env
|
|||||||
.ropeproject/
|
.ropeproject/
|
||||||
.pytest_cache/
|
.pytest_cache/
|
||||||
venv/
|
venv/
|
||||||
|
.vscode/
|
||||||
|
@ -23,6 +23,8 @@ matrix:
|
|||||||
sudo: true
|
sudo: true
|
||||||
before_install:
|
before_install:
|
||||||
- export BOTO_CONFIG=/dev/null
|
- export BOTO_CONFIG=/dev/null
|
||||||
|
- export AWS_SECRET_ACCESS_KEY=foobar_secret
|
||||||
|
- export AWS_ACCESS_KEY_ID=foobar_key
|
||||||
install:
|
install:
|
||||||
# We build moto first so the docker container doesn't try to compile it as well, also note we don't use
|
# 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
|
# -d for docker run so the logs show up in travis
|
||||||
@ -32,8 +34,6 @@ install:
|
|||||||
|
|
||||||
if [ "$TEST_SERVER_MODE" = "true" ]; then
|
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 &
|
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
|
fi
|
||||||
travis_retry pip install boto==2.45.0
|
travis_retry pip install boto==2.45.0
|
||||||
travis_retry pip install boto3
|
travis_retry pip install boto3
|
||||||
|
@ -1,6 +1,11 @@
|
|||||||
Moto Changelog
|
Moto Changelog
|
||||||
===================
|
===================
|
||||||
|
|
||||||
|
1.3.7
|
||||||
|
-----
|
||||||
|
|
||||||
|
* Switch from mocking requests to using before-send for AWS calls
|
||||||
|
|
||||||
1.3.6
|
1.3.6
|
||||||
-----
|
-----
|
||||||
|
|
||||||
|
@ -259,7 +259,7 @@ It uses flask, which isn't a default dependency. You can install the
|
|||||||
server 'extra' package with:
|
server 'extra' package with:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
pip install moto[server]
|
pip install "moto[server]"
|
||||||
```
|
```
|
||||||
|
|
||||||
You can then start it running a service:
|
You can then start it running a service:
|
||||||
|
@ -3,7 +3,7 @@ import logging
|
|||||||
# logging.getLogger('boto').setLevel(logging.CRITICAL)
|
# logging.getLogger('boto').setLevel(logging.CRITICAL)
|
||||||
|
|
||||||
__title__ = 'moto'
|
__title__ = 'moto'
|
||||||
__version__ = '1.3.6'
|
__version__ = '1.3.7'
|
||||||
|
|
||||||
from .acm import mock_acm # flake8: noqa
|
from .acm import mock_acm # flake8: noqa
|
||||||
from .apigateway import mock_apigateway, mock_apigateway_deprecated # flake8: noqa
|
from .apigateway import mock_apigateway, mock_apigateway_deprecated # flake8: noqa
|
||||||
|
@ -10,6 +10,7 @@ from boto3.session import Session
|
|||||||
import responses
|
import responses
|
||||||
from moto.core import BaseBackend, BaseModel
|
from moto.core import BaseBackend, BaseModel
|
||||||
from .utils import create_id
|
from .utils import create_id
|
||||||
|
from moto.core.utils import path_url
|
||||||
from .exceptions import StageNotFoundException, ApiKeyNotFoundException
|
from .exceptions import StageNotFoundException, ApiKeyNotFoundException
|
||||||
|
|
||||||
STAGE_URL = "https://{api_id}.execute-api.{region_name}.amazonaws.com/{stage_name}"
|
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
|
# TODO deal with no matching resource
|
||||||
|
|
||||||
def resource_callback(self, request):
|
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:
|
if not path_after_stage_name:
|
||||||
path_after_stage_name = '/'
|
path_after_stage_name = '/'
|
||||||
|
|
||||||
|
@ -508,6 +508,15 @@ DESCRIBE_AUTOSCALING_GROUPS_TEMPLATE = """<DescribeAutoScalingGroupsResponse xml
|
|||||||
{% else %}
|
{% else %}
|
||||||
<LoadBalancerNames/>
|
<LoadBalancerNames/>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
{% if group.target_group_arns %}
|
||||||
|
<TargetGroupARNs>
|
||||||
|
{% for target_group_arn in group.target_group_arns %}
|
||||||
|
<member>{{ target_group_arn }}</member>
|
||||||
|
{% endfor %}
|
||||||
|
</TargetGroupARNs>
|
||||||
|
{% else %}
|
||||||
|
<TargetGroupARNs/>
|
||||||
|
{% endif %}
|
||||||
<MinSize>{{ group.min_size }}</MinSize>
|
<MinSize>{{ group.min_size }}</MinSize>
|
||||||
{% if group.vpc_zone_identifier %}
|
{% if group.vpc_zone_identifier %}
|
||||||
<VPCZoneIdentifier>{{ group.vpc_zone_identifier }}</VPCZoneIdentifier>
|
<VPCZoneIdentifier>{{ group.vpc_zone_identifier }}</VPCZoneIdentifier>
|
||||||
|
@ -7,7 +7,7 @@ try:
|
|||||||
except ImportError:
|
except ImportError:
|
||||||
from urllib.parse import unquote
|
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 moto.core.responses import BaseResponse
|
||||||
from .models import lambda_backends
|
from .models import lambda_backends
|
||||||
|
|
||||||
@ -94,7 +94,7 @@ class LambdaResponse(BaseResponse):
|
|||||||
return self._add_policy(request, full_url, headers)
|
return self._add_policy(request, full_url, headers)
|
||||||
|
|
||||||
def _add_policy(self, 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]
|
function_name = path.split('/')[-2]
|
||||||
if self.lambda_backend.get_function(function_name):
|
if self.lambda_backend.get_function(function_name):
|
||||||
policy = request.body.decode('utf8')
|
policy = request.body.decode('utf8')
|
||||||
@ -104,7 +104,7 @@ class LambdaResponse(BaseResponse):
|
|||||||
return 404, {}, "{}"
|
return 404, {}, "{}"
|
||||||
|
|
||||||
def _get_policy(self, request, full_url, headers):
|
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]
|
function_name = path.split('/')[-2]
|
||||||
if self.lambda_backend.get_function(function_name):
|
if self.lambda_backend.get_function(function_name):
|
||||||
lambda_function = self.lambda_backend.get_function(function_name)
|
lambda_function = self.lambda_backend.get_function(function_name)
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import datetime
|
import datetime
|
||||||
|
import functools
|
||||||
|
import itertools
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
import time
|
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):
|
class CognitoIdpUserPool(BaseModel):
|
||||||
|
|
||||||
def __init__(self, region, name, extended_config):
|
def __init__(self, region, name, extended_config):
|
||||||
@ -242,7 +277,8 @@ class CognitoIdpBackend(BaseBackend):
|
|||||||
self.user_pools[user_pool.id] = user_pool
|
self.user_pools[user_pool.id] = user_pool
|
||||||
return 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()
|
return self.user_pools.values()
|
||||||
|
|
||||||
def describe_user_pool(self, user_pool_id):
|
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
|
user_pool.clients[user_pool_client.id] = user_pool_client
|
||||||
return 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)
|
user_pool = self.user_pools.get(user_pool_id)
|
||||||
if not user_pool:
|
if not user_pool:
|
||||||
raise ResourceNotFoundError(user_pool_id)
|
raise ResourceNotFoundError(user_pool_id)
|
||||||
@ -339,7 +376,8 @@ class CognitoIdpBackend(BaseBackend):
|
|||||||
user_pool.identity_providers[name] = identity_provider
|
user_pool.identity_providers[name] = identity_provider
|
||||||
return 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)
|
user_pool = self.user_pools.get(user_pool_id)
|
||||||
if not user_pool:
|
if not user_pool:
|
||||||
raise ResourceNotFoundError(user_pool_id)
|
raise ResourceNotFoundError(user_pool_id)
|
||||||
@ -387,7 +425,8 @@ class CognitoIdpBackend(BaseBackend):
|
|||||||
|
|
||||||
return user_pool.users[username]
|
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)
|
user_pool = self.user_pools.get(user_pool_id)
|
||||||
if not user_pool:
|
if not user_pool:
|
||||||
raise ResourceNotFoundError(user_pool_id)
|
raise ResourceNotFoundError(user_pool_id)
|
||||||
|
@ -22,10 +22,17 @@ class CognitoIdpResponse(BaseResponse):
|
|||||||
})
|
})
|
||||||
|
|
||||||
def list_user_pools(self):
|
def list_user_pools(self):
|
||||||
user_pools = cognitoidp_backends[self.region].list_user_pools()
|
max_results = self._get_param("MaxResults")
|
||||||
return json.dumps({
|
next_token = self._get_param("NextToken", "0")
|
||||||
"UserPools": [user_pool.to_json() for user_pool in user_pools]
|
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):
|
def describe_user_pool(self):
|
||||||
user_pool_id = self._get_param("UserPoolId")
|
user_pool_id = self._get_param("UserPoolId")
|
||||||
@ -72,10 +79,16 @@ class CognitoIdpResponse(BaseResponse):
|
|||||||
|
|
||||||
def list_user_pool_clients(self):
|
def list_user_pool_clients(self):
|
||||||
user_pool_id = self._get_param("UserPoolId")
|
user_pool_id = self._get_param("UserPoolId")
|
||||||
user_pool_clients = cognitoidp_backends[self.region].list_user_pool_clients(user_pool_id)
|
max_results = self._get_param("MaxResults")
|
||||||
return json.dumps({
|
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]
|
"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):
|
def describe_user_pool_client(self):
|
||||||
user_pool_id = self._get_param("UserPoolId")
|
user_pool_id = self._get_param("UserPoolId")
|
||||||
@ -110,10 +123,17 @@ class CognitoIdpResponse(BaseResponse):
|
|||||||
|
|
||||||
def list_identity_providers(self):
|
def list_identity_providers(self):
|
||||||
user_pool_id = self._get_param("UserPoolId")
|
user_pool_id = self._get_param("UserPoolId")
|
||||||
identity_providers = cognitoidp_backends[self.region].list_identity_providers(user_pool_id)
|
max_results = self._get_param("MaxResults")
|
||||||
return json.dumps({
|
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]
|
"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):
|
def describe_identity_provider(self):
|
||||||
user_pool_id = self._get_param("UserPoolId")
|
user_pool_id = self._get_param("UserPoolId")
|
||||||
@ -155,10 +175,15 @@ class CognitoIdpResponse(BaseResponse):
|
|||||||
|
|
||||||
def list_users(self):
|
def list_users(self):
|
||||||
user_pool_id = self._get_param("UserPoolId")
|
user_pool_id = self._get_param("UserPoolId")
|
||||||
users = cognitoidp_backends[self.region].list_users(user_pool_id)
|
limit = self._get_param("Limit")
|
||||||
return json.dumps({
|
token = self._get_param("PaginationToken")
|
||||||
"Users": [user.to_json(extended=True) for user in users]
|
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):
|
def admin_disable_user(self):
|
||||||
user_pool_id = self._get_param("UserPoolId")
|
user_pool_id = self._get_param("UserPoolId")
|
||||||
|
@ -2,11 +2,14 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
from __future__ import absolute_import
|
from __future__ import absolute_import
|
||||||
|
|
||||||
from collections import defaultdict
|
|
||||||
import functools
|
import functools
|
||||||
import inspect
|
import inspect
|
||||||
import re
|
import re
|
||||||
import six
|
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
|
from moto import settings
|
||||||
import responses
|
import responses
|
||||||
@ -233,7 +236,111 @@ class ResponsesMockAWS(BaseMockAWS):
|
|||||||
pass
|
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):
|
class ServerModeMockAWS(BaseMockAWS):
|
||||||
|
@ -8,6 +8,7 @@ import random
|
|||||||
import re
|
import re
|
||||||
import six
|
import six
|
||||||
import string
|
import string
|
||||||
|
from six.moves.urllib.parse import urlparse
|
||||||
|
|
||||||
|
|
||||||
REQUEST_ID_LONG = string.digits + string.ascii_uppercase
|
REQUEST_ID_LONG = string.digits + string.ascii_uppercase
|
||||||
@ -286,3 +287,13 @@ def amzn_request_id(f):
|
|||||||
return status, headers, body
|
return status, headers, body
|
||||||
|
|
||||||
return _wrapper
|
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
|
||||||
|
@ -8,4 +8,6 @@ class ServiceNotFoundException(RESTError):
|
|||||||
def __init__(self, service_name):
|
def __init__(self, service_name):
|
||||||
super(ServiceNotFoundException, self).__init__(
|
super(ServiceNotFoundException, self).__init__(
|
||||||
error_type="ServiceNotFoundException",
|
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',
|
||||||
|
)
|
||||||
|
@ -613,13 +613,11 @@ DESCRIBE_STEP_TEMPLATE = """<DescribeStepResponse xmlns="http://elasticmapreduce
|
|||||||
<Id>{{ step.id }}</Id>
|
<Id>{{ step.id }}</Id>
|
||||||
<Name>{{ step.name | escape }}</Name>
|
<Name>{{ step.name | escape }}</Name>
|
||||||
<Status>
|
<Status>
|
||||||
<!-- does not exist for botocore 1.4.28
|
|
||||||
<FailureDetails>
|
<FailureDetails>
|
||||||
<Reason/>
|
<Reason/>
|
||||||
<Message/>
|
<Message/>
|
||||||
<LogFile/>
|
<LogFile/>
|
||||||
</FailureDetails>
|
</FailureDetails>
|
||||||
-->
|
|
||||||
<State>{{ step.state }}</State>
|
<State>{{ step.state }}</State>
|
||||||
<StateChangeReason>{{ step.state_change_reason }}</StateChangeReason>
|
<StateChangeReason>{{ step.state_change_reason }}</StateChangeReason>
|
||||||
<Timeline>
|
<Timeline>
|
||||||
|
@ -24,3 +24,11 @@ class IAMReportNotPresentException(RESTError):
|
|||||||
def __init__(self, message):
|
def __init__(self, message):
|
||||||
super(IAMReportNotPresentException, self).__init__(
|
super(IAMReportNotPresentException, self).__init__(
|
||||||
"ReportNotPresent", message)
|
"ReportNotPresent", message)
|
||||||
|
|
||||||
|
|
||||||
|
class MalformedCertificate(RESTError):
|
||||||
|
code = 400
|
||||||
|
|
||||||
|
def __init__(self, cert):
|
||||||
|
super(MalformedCertificate, self).__init__(
|
||||||
|
'MalformedCertificate', 'Certificate {cert} is malformed'.format(cert=cert))
|
||||||
|
@ -1,14 +1,18 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
import base64
|
import base64
|
||||||
|
import sys
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
import json
|
import json
|
||||||
|
|
||||||
|
from cryptography import x509
|
||||||
|
from cryptography.hazmat.backends import default_backend
|
||||||
|
|
||||||
import pytz
|
import pytz
|
||||||
from moto.core import BaseBackend, BaseModel
|
from moto.core import BaseBackend, BaseModel
|
||||||
from moto.core.utils import iso_8601_datetime_without_milliseconds
|
from moto.core.utils import iso_8601_datetime_without_milliseconds
|
||||||
|
|
||||||
from .aws_managed_policies import aws_managed_policies_data
|
from .aws_managed_policies import aws_managed_policies_data
|
||||||
from .exceptions import IAMNotFoundException, IAMConflictException, IAMReportNotPresentException
|
from .exceptions import IAMNotFoundException, IAMConflictException, IAMReportNotPresentException, MalformedCertificate
|
||||||
from .utils import random_access_key, random_alphanumeric, random_resource_id, random_policy_id
|
from .utils import random_access_key, random_alphanumeric, random_resource_id, random_policy_id
|
||||||
|
|
||||||
ACCOUNT_ID = 123456789012
|
ACCOUNT_ID = 123456789012
|
||||||
@ -117,6 +121,7 @@ class Role(BaseModel):
|
|||||||
self.path = path
|
self.path = path
|
||||||
self.policies = {}
|
self.policies = {}
|
||||||
self.managed_policies = {}
|
self.managed_policies = {}
|
||||||
|
self.create_date = datetime.now(pytz.utc)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def create_from_cloudformation_json(cls, resource_name, cloudformation_json, region_name):
|
def create_from_cloudformation_json(cls, resource_name, cloudformation_json, region_name):
|
||||||
@ -168,6 +173,7 @@ class InstanceProfile(BaseModel):
|
|||||||
self.name = name
|
self.name = name
|
||||||
self.path = path
|
self.path = path
|
||||||
self.roles = roles if roles else []
|
self.roles = roles if roles else []
|
||||||
|
self.create_date = datetime.now(pytz.utc)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def create_from_cloudformation_json(cls, resource_name, cloudformation_json, region_name):
|
def create_from_cloudformation_json(cls, resource_name, cloudformation_json, region_name):
|
||||||
@ -213,6 +219,16 @@ class Certificate(BaseModel):
|
|||||||
return "arn:aws:iam::{0}:server-certificate{1}{2}".format(ACCOUNT_ID, self.path, self.cert_name)
|
return "arn:aws:iam::{0}:server-certificate{1}{2}".format(ACCOUNT_ID, self.path, self.cert_name)
|
||||||
|
|
||||||
|
|
||||||
|
class SigningCertificate(BaseModel):
|
||||||
|
|
||||||
|
def __init__(self, id, user_name, body):
|
||||||
|
self.id = id
|
||||||
|
self.user_name = user_name
|
||||||
|
self.body = body
|
||||||
|
self.upload_date = datetime.strftime(datetime.utcnow(), "%Y-%m-%d-%H-%M-%S")
|
||||||
|
self.status = 'Active'
|
||||||
|
|
||||||
|
|
||||||
class AccessKey(BaseModel):
|
class AccessKey(BaseModel):
|
||||||
|
|
||||||
def __init__(self, user_name):
|
def __init__(self, user_name):
|
||||||
@ -297,6 +313,7 @@ class User(BaseModel):
|
|||||||
self.access_keys = []
|
self.access_keys = []
|
||||||
self.password = None
|
self.password = None
|
||||||
self.password_reset_required = False
|
self.password_reset_required = False
|
||||||
|
self.signing_certificates = {}
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def arn(self):
|
def arn(self):
|
||||||
@ -765,6 +782,48 @@ class IAMBackend(BaseBackend):
|
|||||||
|
|
||||||
return users
|
return users
|
||||||
|
|
||||||
|
def upload_signing_certificate(self, user_name, body):
|
||||||
|
user = self.get_user(user_name)
|
||||||
|
cert_id = random_resource_id(size=32)
|
||||||
|
|
||||||
|
# Validate the signing cert:
|
||||||
|
try:
|
||||||
|
if sys.version_info < (3, 0):
|
||||||
|
data = bytes(body)
|
||||||
|
else:
|
||||||
|
data = bytes(body, 'utf8')
|
||||||
|
|
||||||
|
x509.load_pem_x509_certificate(data, default_backend())
|
||||||
|
|
||||||
|
except Exception:
|
||||||
|
raise MalformedCertificate(body)
|
||||||
|
|
||||||
|
user.signing_certificates[cert_id] = SigningCertificate(cert_id, user_name, body)
|
||||||
|
|
||||||
|
return user.signing_certificates[cert_id]
|
||||||
|
|
||||||
|
def delete_signing_certificate(self, user_name, cert_id):
|
||||||
|
user = self.get_user(user_name)
|
||||||
|
|
||||||
|
try:
|
||||||
|
del user.signing_certificates[cert_id]
|
||||||
|
except KeyError:
|
||||||
|
raise IAMNotFoundException("The Certificate with id {id} cannot be found.".format(id=cert_id))
|
||||||
|
|
||||||
|
def list_signing_certificates(self, user_name):
|
||||||
|
user = self.get_user(user_name)
|
||||||
|
|
||||||
|
return list(user.signing_certificates.values())
|
||||||
|
|
||||||
|
def update_signing_certificate(self, user_name, cert_id, status):
|
||||||
|
user = self.get_user(user_name)
|
||||||
|
|
||||||
|
try:
|
||||||
|
user.signing_certificates[cert_id].status = status
|
||||||
|
|
||||||
|
except KeyError:
|
||||||
|
raise IAMNotFoundException("The Certificate with id {id} cannot be found.".format(id=cert_id))
|
||||||
|
|
||||||
def create_login_profile(self, user_name, password):
|
def create_login_profile(self, user_name, password):
|
||||||
# This does not currently deal with PasswordPolicyViolation.
|
# This does not currently deal with PasswordPolicyViolation.
|
||||||
user = self.get_user(user_name)
|
user = self.get_user(user_name)
|
||||||
|
@ -201,7 +201,7 @@ class IamResponse(BaseResponse):
|
|||||||
|
|
||||||
def create_instance_profile(self):
|
def create_instance_profile(self):
|
||||||
profile_name = self._get_param('InstanceProfileName')
|
profile_name = self._get_param('InstanceProfileName')
|
||||||
path = self._get_param('Path')
|
path = self._get_param('Path', '/')
|
||||||
|
|
||||||
profile = iam_backend.create_instance_profile(
|
profile = iam_backend.create_instance_profile(
|
||||||
profile_name, path, role_ids=[])
|
profile_name, path, role_ids=[])
|
||||||
@ -552,6 +552,38 @@ class IamResponse(BaseResponse):
|
|||||||
roles=account_details['roles']
|
roles=account_details['roles']
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def upload_signing_certificate(self):
|
||||||
|
user_name = self._get_param('UserName')
|
||||||
|
cert_body = self._get_param('CertificateBody')
|
||||||
|
|
||||||
|
cert = iam_backend.upload_signing_certificate(user_name, cert_body)
|
||||||
|
template = self.response_template(UPLOAD_SIGNING_CERTIFICATE_TEMPLATE)
|
||||||
|
return template.render(cert=cert)
|
||||||
|
|
||||||
|
def update_signing_certificate(self):
|
||||||
|
user_name = self._get_param('UserName')
|
||||||
|
cert_id = self._get_param('CertificateId')
|
||||||
|
status = self._get_param('Status')
|
||||||
|
|
||||||
|
iam_backend.update_signing_certificate(user_name, cert_id, status)
|
||||||
|
template = self.response_template(UPDATE_SIGNING_CERTIFICATE_TEMPLATE)
|
||||||
|
return template.render()
|
||||||
|
|
||||||
|
def delete_signing_certificate(self):
|
||||||
|
user_name = self._get_param('UserName')
|
||||||
|
cert_id = self._get_param('CertificateId')
|
||||||
|
|
||||||
|
iam_backend.delete_signing_certificate(user_name, cert_id)
|
||||||
|
template = self.response_template(DELETE_SIGNING_CERTIFICATE_TEMPLATE)
|
||||||
|
return template.render()
|
||||||
|
|
||||||
|
def list_signing_certificates(self):
|
||||||
|
user_name = self._get_param('UserName')
|
||||||
|
|
||||||
|
certs = iam_backend.list_signing_certificates(user_name)
|
||||||
|
template = self.response_template(LIST_SIGNING_CERTIFICATES_TEMPLATE)
|
||||||
|
return template.render(user_name=user_name, certificates=certs)
|
||||||
|
|
||||||
|
|
||||||
ATTACH_ROLE_POLICY_TEMPLATE = """<AttachRolePolicyResponse>
|
ATTACH_ROLE_POLICY_TEMPLATE = """<AttachRolePolicyResponse>
|
||||||
<ResponseMetadata>
|
<ResponseMetadata>
|
||||||
@ -734,7 +766,7 @@ CREATE_INSTANCE_PROFILE_TEMPLATE = """<CreateInstanceProfileResponse xmlns="http
|
|||||||
<InstanceProfileName>{{ profile.name }}</InstanceProfileName>
|
<InstanceProfileName>{{ profile.name }}</InstanceProfileName>
|
||||||
<Path>{{ profile.path }}</Path>
|
<Path>{{ profile.path }}</Path>
|
||||||
<Arn>{{ profile.arn }}</Arn>
|
<Arn>{{ profile.arn }}</Arn>
|
||||||
<CreateDate>2012-05-09T16:11:10.222Z</CreateDate>
|
<CreateDate>{{ profile.create_date }}</CreateDate>
|
||||||
</InstanceProfile>
|
</InstanceProfile>
|
||||||
</CreateInstanceProfileResult>
|
</CreateInstanceProfileResult>
|
||||||
<ResponseMetadata>
|
<ResponseMetadata>
|
||||||
@ -753,7 +785,7 @@ GET_INSTANCE_PROFILE_TEMPLATE = """<GetInstanceProfileResponse xmlns="https://ia
|
|||||||
<Arn>{{ role.arn }}</Arn>
|
<Arn>{{ role.arn }}</Arn>
|
||||||
<RoleName>{{ role.name }}</RoleName>
|
<RoleName>{{ role.name }}</RoleName>
|
||||||
<AssumeRolePolicyDocument>{{ role.assume_role_policy_document }}</AssumeRolePolicyDocument>
|
<AssumeRolePolicyDocument>{{ role.assume_role_policy_document }}</AssumeRolePolicyDocument>
|
||||||
<CreateDate>2012-05-09T15:45:35Z</CreateDate>
|
<CreateDate>{{ role.create_date }}</CreateDate>
|
||||||
<RoleId>{{ role.id }}</RoleId>
|
<RoleId>{{ role.id }}</RoleId>
|
||||||
</member>
|
</member>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
@ -761,7 +793,7 @@ GET_INSTANCE_PROFILE_TEMPLATE = """<GetInstanceProfileResponse xmlns="https://ia
|
|||||||
<InstanceProfileName>{{ profile.name }}</InstanceProfileName>
|
<InstanceProfileName>{{ profile.name }}</InstanceProfileName>
|
||||||
<Path>{{ profile.path }}</Path>
|
<Path>{{ profile.path }}</Path>
|
||||||
<Arn>{{ profile.arn }}</Arn>
|
<Arn>{{ profile.arn }}</Arn>
|
||||||
<CreateDate>2012-05-09T16:11:10Z</CreateDate>
|
<CreateDate>{{ profile.create_date }}</CreateDate>
|
||||||
</InstanceProfile>
|
</InstanceProfile>
|
||||||
</GetInstanceProfileResult>
|
</GetInstanceProfileResult>
|
||||||
<ResponseMetadata>
|
<ResponseMetadata>
|
||||||
@ -776,7 +808,7 @@ CREATE_ROLE_TEMPLATE = """<CreateRoleResponse xmlns="https://iam.amazonaws.com/d
|
|||||||
<Arn>{{ role.arn }}</Arn>
|
<Arn>{{ role.arn }}</Arn>
|
||||||
<RoleName>{{ role.name }}</RoleName>
|
<RoleName>{{ role.name }}</RoleName>
|
||||||
<AssumeRolePolicyDocument>{{ role.assume_role_policy_document }}</AssumeRolePolicyDocument>
|
<AssumeRolePolicyDocument>{{ role.assume_role_policy_document }}</AssumeRolePolicyDocument>
|
||||||
<CreateDate>2012-05-08T23:34:01.495Z</CreateDate>
|
<CreateDate>{{ role.create_date }}</CreateDate>
|
||||||
<RoleId>{{ role.id }}</RoleId>
|
<RoleId>{{ role.id }}</RoleId>
|
||||||
</Role>
|
</Role>
|
||||||
</CreateRoleResult>
|
</CreateRoleResult>
|
||||||
@ -803,7 +835,7 @@ GET_ROLE_TEMPLATE = """<GetRoleResponse xmlns="https://iam.amazonaws.com/doc/201
|
|||||||
<Arn>{{ role.arn }}</Arn>
|
<Arn>{{ role.arn }}</Arn>
|
||||||
<RoleName>{{ role.name }}</RoleName>
|
<RoleName>{{ role.name }}</RoleName>
|
||||||
<AssumeRolePolicyDocument>{{ role.assume_role_policy_document }}</AssumeRolePolicyDocument>
|
<AssumeRolePolicyDocument>{{ role.assume_role_policy_document }}</AssumeRolePolicyDocument>
|
||||||
<CreateDate>2012-05-08T23:34:01Z</CreateDate>
|
<CreateDate>{{ role.create_date }}</CreateDate>
|
||||||
<RoleId>{{ role.id }}</RoleId>
|
<RoleId>{{ role.id }}</RoleId>
|
||||||
</Role>
|
</Role>
|
||||||
</GetRoleResult>
|
</GetRoleResult>
|
||||||
@ -834,7 +866,7 @@ LIST_ROLES_TEMPLATE = """<ListRolesResponse xmlns="https://iam.amazonaws.com/doc
|
|||||||
<Arn>{{ role.arn }}</Arn>
|
<Arn>{{ role.arn }}</Arn>
|
||||||
<RoleName>{{ role.name }}</RoleName>
|
<RoleName>{{ role.name }}</RoleName>
|
||||||
<AssumeRolePolicyDocument>{{ role.assume_role_policy_document }}</AssumeRolePolicyDocument>
|
<AssumeRolePolicyDocument>{{ role.assume_role_policy_document }}</AssumeRolePolicyDocument>
|
||||||
<CreateDate>2012-05-09T15:45:35Z</CreateDate>
|
<CreateDate>{{ role.create_date }}</CreateDate>
|
||||||
<RoleId>{{ role.id }}</RoleId>
|
<RoleId>{{ role.id }}</RoleId>
|
||||||
</member>
|
</member>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
@ -865,7 +897,7 @@ CREATE_POLICY_VERSION_TEMPLATE = """<CreatePolicyVersionResponse xmlns="https://
|
|||||||
<Document>{{ policy_version.document }}</Document>
|
<Document>{{ policy_version.document }}</Document>
|
||||||
<VersionId>{{ policy_version.version_id }}</VersionId>
|
<VersionId>{{ policy_version.version_id }}</VersionId>
|
||||||
<IsDefaultVersion>{{ policy_version.is_default }}</IsDefaultVersion>
|
<IsDefaultVersion>{{ policy_version.is_default }}</IsDefaultVersion>
|
||||||
<CreateDate>2012-05-09T15:45:35Z</CreateDate>
|
<CreateDate>{{ policy_version.create_datetime }}</CreateDate>
|
||||||
</PolicyVersion>
|
</PolicyVersion>
|
||||||
</CreatePolicyVersionResult>
|
</CreatePolicyVersionResult>
|
||||||
<ResponseMetadata>
|
<ResponseMetadata>
|
||||||
@ -879,7 +911,7 @@ GET_POLICY_VERSION_TEMPLATE = """<GetPolicyVersionResponse xmlns="https://iam.am
|
|||||||
<Document>{{ policy_version.document }}</Document>
|
<Document>{{ policy_version.document }}</Document>
|
||||||
<VersionId>{{ policy_version.version_id }}</VersionId>
|
<VersionId>{{ policy_version.version_id }}</VersionId>
|
||||||
<IsDefaultVersion>{{ policy_version.is_default }}</IsDefaultVersion>
|
<IsDefaultVersion>{{ policy_version.is_default }}</IsDefaultVersion>
|
||||||
<CreateDate>2012-05-09T15:45:35Z</CreateDate>
|
<CreateDate>{{ policy_version.create_datetime }}</CreateDate>
|
||||||
</PolicyVersion>
|
</PolicyVersion>
|
||||||
</GetPolicyVersionResult>
|
</GetPolicyVersionResult>
|
||||||
<ResponseMetadata>
|
<ResponseMetadata>
|
||||||
@ -896,7 +928,7 @@ LIST_POLICY_VERSIONS_TEMPLATE = """<ListPolicyVersionsResponse xmlns="https://ia
|
|||||||
<Document>{{ policy_version.document }}</Document>
|
<Document>{{ policy_version.document }}</Document>
|
||||||
<VersionId>{{ policy_version.version_id }}</VersionId>
|
<VersionId>{{ policy_version.version_id }}</VersionId>
|
||||||
<IsDefaultVersion>{{ policy_version.is_default }}</IsDefaultVersion>
|
<IsDefaultVersion>{{ policy_version.is_default }}</IsDefaultVersion>
|
||||||
<CreateDate>2012-05-09T15:45:35Z</CreateDate>
|
<CreateDate>{{ policy_version.create_datetime }}</CreateDate>
|
||||||
</member>
|
</member>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</Versions>
|
</Versions>
|
||||||
@ -912,7 +944,7 @@ LIST_INSTANCE_PROFILES_TEMPLATE = """<ListInstanceProfilesResponse xmlns="https:
|
|||||||
<InstanceProfiles>
|
<InstanceProfiles>
|
||||||
{% for instance in instance_profiles %}
|
{% for instance in instance_profiles %}
|
||||||
<member>
|
<member>
|
||||||
<Id>{{ instance.id }}</Id>
|
<InstanceProfileId>{{ instance.id }}</InstanceProfileId>
|
||||||
<Roles>
|
<Roles>
|
||||||
{% for role in instance.roles %}
|
{% for role in instance.roles %}
|
||||||
<member>
|
<member>
|
||||||
@ -920,7 +952,7 @@ LIST_INSTANCE_PROFILES_TEMPLATE = """<ListInstanceProfilesResponse xmlns="https:
|
|||||||
<Arn>{{ role.arn }}</Arn>
|
<Arn>{{ role.arn }}</Arn>
|
||||||
<RoleName>{{ role.name }}</RoleName>
|
<RoleName>{{ role.name }}</RoleName>
|
||||||
<AssumeRolePolicyDocument>{{ role.assume_role_policy_document }}</AssumeRolePolicyDocument>
|
<AssumeRolePolicyDocument>{{ role.assume_role_policy_document }}</AssumeRolePolicyDocument>
|
||||||
<CreateDate>2012-05-09T15:45:35Z</CreateDate>
|
<CreateDate>{{ role.create_date }}</CreateDate>
|
||||||
<RoleId>{{ role.id }}</RoleId>
|
<RoleId>{{ role.id }}</RoleId>
|
||||||
</member>
|
</member>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
@ -928,7 +960,7 @@ LIST_INSTANCE_PROFILES_TEMPLATE = """<ListInstanceProfilesResponse xmlns="https:
|
|||||||
<InstanceProfileName>{{ instance.name }}</InstanceProfileName>
|
<InstanceProfileName>{{ instance.name }}</InstanceProfileName>
|
||||||
<Path>{{ instance.path }}</Path>
|
<Path>{{ instance.path }}</Path>
|
||||||
<Arn>{{ instance.arn }}</Arn>
|
<Arn>{{ instance.arn }}</Arn>
|
||||||
<CreateDate>2012-05-09T16:27:03Z</CreateDate>
|
<CreateDate>{{ instance.create_date }}</CreateDate>
|
||||||
</member>
|
</member>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</InstanceProfiles>
|
</InstanceProfiles>
|
||||||
@ -1199,8 +1231,8 @@ LIST_USER_POLICIES_TEMPLATE = """<ListUserPoliciesResponse>
|
|||||||
<member>{{ policy }}</member>
|
<member>{{ policy }}</member>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</PolicyNames>
|
</PolicyNames>
|
||||||
</ListUserPoliciesResult>
|
|
||||||
<IsTruncated>false</IsTruncated>
|
<IsTruncated>false</IsTruncated>
|
||||||
|
</ListUserPoliciesResult>
|
||||||
<ResponseMetadata>
|
<ResponseMetadata>
|
||||||
<RequestId>7a62c49f-347e-4fc4-9331-6e8eEXAMPLE</RequestId>
|
<RequestId>7a62c49f-347e-4fc4-9331-6e8eEXAMPLE</RequestId>
|
||||||
</ResponseMetadata>
|
</ResponseMetadata>
|
||||||
@ -1277,7 +1309,7 @@ LIST_INSTANCE_PROFILES_FOR_ROLE_TEMPLATE = """<ListInstanceProfilesForRoleRespon
|
|||||||
<InstanceProfiles>
|
<InstanceProfiles>
|
||||||
{% for profile in instance_profiles %}
|
{% for profile in instance_profiles %}
|
||||||
<member>
|
<member>
|
||||||
<Id>{{ profile.id }}</Id>
|
<InstanceProfileId>{{ profile.id }}</InstanceProfileId>
|
||||||
<Roles>
|
<Roles>
|
||||||
{% for role in profile.roles %}
|
{% for role in profile.roles %}
|
||||||
<member>
|
<member>
|
||||||
@ -1285,7 +1317,7 @@ LIST_INSTANCE_PROFILES_FOR_ROLE_TEMPLATE = """<ListInstanceProfilesForRoleRespon
|
|||||||
<Arn>{{ role.arn }}</Arn>
|
<Arn>{{ role.arn }}</Arn>
|
||||||
<RoleName>{{ role.name }}</RoleName>
|
<RoleName>{{ role.name }}</RoleName>
|
||||||
<AssumeRolePolicyDocument>{{ role.assume_policy_document }}</AssumeRolePolicyDocument>
|
<AssumeRolePolicyDocument>{{ role.assume_policy_document }}</AssumeRolePolicyDocument>
|
||||||
<CreateDate>2012-05-09T15:45:35Z</CreateDate>
|
<CreateDate>{{ role.create_date }}</CreateDate>
|
||||||
<RoleId>{{ role.id }}</RoleId>
|
<RoleId>{{ role.id }}</RoleId>
|
||||||
</member>
|
</member>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
@ -1293,7 +1325,7 @@ LIST_INSTANCE_PROFILES_FOR_ROLE_TEMPLATE = """<ListInstanceProfilesForRoleRespon
|
|||||||
<InstanceProfileName>{{ profile.name }}</InstanceProfileName>
|
<InstanceProfileName>{{ profile.name }}</InstanceProfileName>
|
||||||
<Path>{{ profile.path }}</Path>
|
<Path>{{ profile.path }}</Path>
|
||||||
<Arn>{{ profile.arn }}</Arn>
|
<Arn>{{ profile.arn }}</Arn>
|
||||||
<CreateDate>2012-05-09T16:27:11Z</CreateDate>
|
<CreateDate>{{ profile.create_date }}</CreateDate>
|
||||||
</member>
|
</member>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</InstanceProfiles>
|
</InstanceProfiles>
|
||||||
@ -1382,7 +1414,7 @@ GET_ACCOUNT_AUTHORIZATION_DETAILS_TEMPLATE = """<GetAccountAuthorizationDetailsR
|
|||||||
<Path>{{ user.path }}</Path>
|
<Path>{{ user.path }}</Path>
|
||||||
<UserName>{{ user.name }}</UserName>
|
<UserName>{{ user.name }}</UserName>
|
||||||
<Arn>{{ user.arn }}</Arn>
|
<Arn>{{ user.arn }}</Arn>
|
||||||
<CreateDate>2012-05-09T15:45:35Z</CreateDate>
|
<CreateDate>{{ user.created_iso_8601 }}</CreateDate>
|
||||||
</member>
|
</member>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</UserDetailList>
|
</UserDetailList>
|
||||||
@ -1401,7 +1433,7 @@ GET_ACCOUNT_AUTHORIZATION_DETAILS_TEMPLATE = """<GetAccountAuthorizationDetailsR
|
|||||||
<GroupName>{{ group.name }}</GroupName>
|
<GroupName>{{ group.name }}</GroupName>
|
||||||
<Path>{{ group.path }}</Path>
|
<Path>{{ group.path }}</Path>
|
||||||
<Arn>{{ group.arn }}</Arn>
|
<Arn>{{ group.arn }}</Arn>
|
||||||
<CreateDate>2012-05-09T16:27:11Z</CreateDate>
|
<CreateDate>{{ group.create_date }}</CreateDate>
|
||||||
<GroupPolicyList/>
|
<GroupPolicyList/>
|
||||||
</member>
|
</member>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
@ -1421,7 +1453,7 @@ GET_ACCOUNT_AUTHORIZATION_DETAILS_TEMPLATE = """<GetAccountAuthorizationDetailsR
|
|||||||
<InstanceProfileList>
|
<InstanceProfileList>
|
||||||
{% for profile in instance_profiles %}
|
{% for profile in instance_profiles %}
|
||||||
<member>
|
<member>
|
||||||
<Id>{{ profile.id }}</Id>
|
<InstanceProfileId>{{ profile.id }}</InstanceProfileId>
|
||||||
<Roles>
|
<Roles>
|
||||||
{% for role in profile.roles %}
|
{% for role in profile.roles %}
|
||||||
<member>
|
<member>
|
||||||
@ -1429,7 +1461,7 @@ GET_ACCOUNT_AUTHORIZATION_DETAILS_TEMPLATE = """<GetAccountAuthorizationDetailsR
|
|||||||
<Arn>{{ role.arn }}</Arn>
|
<Arn>{{ role.arn }}</Arn>
|
||||||
<RoleName>{{ role.name }}</RoleName>
|
<RoleName>{{ role.name }}</RoleName>
|
||||||
<AssumeRolePolicyDocument>{{ role.assume_role_policy_document }}</AssumeRolePolicyDocument>
|
<AssumeRolePolicyDocument>{{ role.assume_role_policy_document }}</AssumeRolePolicyDocument>
|
||||||
<CreateDate>2012-05-09T15:45:35Z</CreateDate>
|
<CreateDate>{{ role.create_date }}</CreateDate>
|
||||||
<RoleId>{{ role.id }}</RoleId>
|
<RoleId>{{ role.id }}</RoleId>
|
||||||
</member>
|
</member>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
@ -1437,7 +1469,7 @@ GET_ACCOUNT_AUTHORIZATION_DETAILS_TEMPLATE = """<GetAccountAuthorizationDetailsR
|
|||||||
<InstanceProfileName>{{ profile.name }}</InstanceProfileName>
|
<InstanceProfileName>{{ profile.name }}</InstanceProfileName>
|
||||||
<Path>{{ profile.path }}</Path>
|
<Path>{{ profile.path }}</Path>
|
||||||
<Arn>{{ profile.arn }}</Arn>
|
<Arn>{{ profile.arn }}</Arn>
|
||||||
<CreateDate>2012-05-09T16:27:11Z</CreateDate>
|
<CreateDate>{{ profile.create_date }}</CreateDate>
|
||||||
</member>
|
</member>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</InstanceProfileList>
|
</InstanceProfileList>
|
||||||
@ -1445,7 +1477,7 @@ GET_ACCOUNT_AUTHORIZATION_DETAILS_TEMPLATE = """<GetAccountAuthorizationDetailsR
|
|||||||
<Arn>{{ role.arn }}</Arn>
|
<Arn>{{ role.arn }}</Arn>
|
||||||
<RoleName>{{ role.name }}</RoleName>
|
<RoleName>{{ role.name }}</RoleName>
|
||||||
<AssumeRolePolicyDocument>{{ role.assume_role_policy_document }}</AssumeRolePolicyDocument>
|
<AssumeRolePolicyDocument>{{ role.assume_role_policy_document }}</AssumeRolePolicyDocument>
|
||||||
<CreateDate>2014-07-30T17:09:20Z</CreateDate>
|
<CreateDate>{{ role.create_date }}</CreateDate>
|
||||||
<RoleId>{{ role.id }}</RoleId>
|
<RoleId>{{ role.id }}</RoleId>
|
||||||
</member>
|
</member>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
@ -1474,9 +1506,9 @@ GET_ACCOUNT_AUTHORIZATION_DETAILS_TEMPLATE = """<GetAccountAuthorizationDetailsR
|
|||||||
</PolicyVersionList>
|
</PolicyVersionList>
|
||||||
<Arn>{{ policy.arn }}</Arn>
|
<Arn>{{ policy.arn }}</Arn>
|
||||||
<AttachmentCount>1</AttachmentCount>
|
<AttachmentCount>1</AttachmentCount>
|
||||||
<CreateDate>2012-05-09T16:27:11Z</CreateDate>
|
<CreateDate>{{ policy.create_datetime }}</CreateDate>
|
||||||
<IsAttachable>true</IsAttachable>
|
<IsAttachable>true</IsAttachable>
|
||||||
<UpdateDate>2012-05-09T16:27:11Z</UpdateDate>
|
<UpdateDate>{{ policy.update_datetime }}</UpdateDate>
|
||||||
</member>
|
</member>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</Policies>
|
</Policies>
|
||||||
@ -1485,3 +1517,53 @@ GET_ACCOUNT_AUTHORIZATION_DETAILS_TEMPLATE = """<GetAccountAuthorizationDetailsR
|
|||||||
<RequestId>92e79ae7-7399-11e4-8c85-4b53eEXAMPLE</RequestId>
|
<RequestId>92e79ae7-7399-11e4-8c85-4b53eEXAMPLE</RequestId>
|
||||||
</ResponseMetadata>
|
</ResponseMetadata>
|
||||||
</GetAccountAuthorizationDetailsResponse>"""
|
</GetAccountAuthorizationDetailsResponse>"""
|
||||||
|
|
||||||
|
|
||||||
|
UPLOAD_SIGNING_CERTIFICATE_TEMPLATE = """<UploadSigningCertificateResponse xmlns="https://iam.amazonaws.com/doc/2010-05-08/">
|
||||||
|
<UploadSigningCertificateResult>
|
||||||
|
<Certificate>
|
||||||
|
<UserName>{{ cert.user_name }}</UserName>
|
||||||
|
<CertificateId>{{ cert.id }}</CertificateId>
|
||||||
|
<CertificateBody>{{ cert.body }}</CertificateBody>
|
||||||
|
<Status>{{ cert.status }}</Status>
|
||||||
|
</Certificate>
|
||||||
|
</UploadSigningCertificateResult>
|
||||||
|
<ResponseMetadata>
|
||||||
|
<RequestId>7a62c49f-347e-4fc4-9331-6e8eEXAMPLE</RequestId>
|
||||||
|
</ResponseMetadata>
|
||||||
|
</UploadSigningCertificateResponse>"""
|
||||||
|
|
||||||
|
|
||||||
|
UPDATE_SIGNING_CERTIFICATE_TEMPLATE = """<UpdateSigningCertificateResponse xmlns="https://iam.amazonaws.com/doc/2010-05-08/">
|
||||||
|
<ResponseMetadata>
|
||||||
|
<RequestId>EXAMPLE8-90ab-cdef-fedc-ba987EXAMPLE</RequestId>
|
||||||
|
</ResponseMetadata>
|
||||||
|
</UpdateSigningCertificateResponse>"""
|
||||||
|
|
||||||
|
|
||||||
|
DELETE_SIGNING_CERTIFICATE_TEMPLATE = """<DeleteSigningCertificateResponse xmlns="https://iam.amazonaws.com/doc/2010-05-08/">
|
||||||
|
<ResponseMetadata>
|
||||||
|
<RequestId>7a62c49f-347e-4fc4-9331-6e8eEXAMPLE</RequestId>
|
||||||
|
</ResponseMetadata>
|
||||||
|
</DeleteSigningCertificateResponse>"""
|
||||||
|
|
||||||
|
|
||||||
|
LIST_SIGNING_CERTIFICATES_TEMPLATE = """<ListSigningCertificatesResponse>
|
||||||
|
<ListSigningCertificatesResult>
|
||||||
|
<UserName>{{ user_name }}</UserName>
|
||||||
|
<Certificates>
|
||||||
|
{% for cert in certificates %}
|
||||||
|
<member>
|
||||||
|
<UserName>{{ user_name }}</UserName>
|
||||||
|
<CertificateId>{{ cert.id }}</CertificateId>
|
||||||
|
<CertificateBody>{{ cert.body }}</CertificateBody>
|
||||||
|
<Status>{{ cert.status }}</Status>
|
||||||
|
</member>
|
||||||
|
{% endfor %}
|
||||||
|
</Certificates>
|
||||||
|
<IsTruncated>false</IsTruncated>
|
||||||
|
</ListSigningCertificatesResult>
|
||||||
|
<ResponseMetadata>
|
||||||
|
<RequestId>7a62c49f-347e-4fc4-9331-6e8eEXAMPLE</RequestId>
|
||||||
|
</ResponseMetadata>
|
||||||
|
</ListSigningCertificatesResponse>"""
|
||||||
|
@ -12,8 +12,7 @@ def random_alphanumeric(length):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def random_resource_id():
|
def random_resource_id(size=20):
|
||||||
size = 20
|
|
||||||
chars = list(range(10)) + list(string.ascii_lowercase)
|
chars = list(range(10)) + list(string.ascii_lowercase)
|
||||||
|
|
||||||
return ''.join(six.text_type(random.choice(chars)) for x in range(size))
|
return ''.join(six.text_type(random.choice(chars)) for x in range(size))
|
||||||
|
@ -178,3 +178,13 @@ class InvalidStorageClass(S3ClientError):
|
|||||||
"InvalidStorageClass",
|
"InvalidStorageClass",
|
||||||
"The storage class you specified is not valid",
|
"The storage class you specified is not valid",
|
||||||
*args, **kwargs)
|
*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)
|
||||||
|
@ -15,7 +15,7 @@ from bisect import insort
|
|||||||
from moto.core import BaseBackend, BaseModel
|
from moto.core import BaseBackend, BaseModel
|
||||||
from moto.core.utils import iso_8601_datetime_with_milliseconds, rfc_1123_datetime
|
from moto.core.utils import iso_8601_datetime_with_milliseconds, rfc_1123_datetime
|
||||||
from .exceptions import BucketAlreadyExists, MissingBucket, InvalidPart, EntityTooSmall, MissingKey, \
|
from .exceptions import BucketAlreadyExists, MissingBucket, InvalidPart, EntityTooSmall, MissingKey, \
|
||||||
InvalidNotificationDestination, MalformedXML, InvalidStorageClass
|
InvalidNotificationDestination, MalformedXML, InvalidStorageClass, DuplicateTagKeys
|
||||||
from .utils import clean_key_name, _VersionedKeyStore
|
from .utils import clean_key_name, _VersionedKeyStore
|
||||||
|
|
||||||
UPLOAD_ID_BYTES = 43
|
UPLOAD_ID_BYTES = 43
|
||||||
@ -773,6 +773,9 @@ class S3Backend(BaseBackend):
|
|||||||
return key
|
return key
|
||||||
|
|
||||||
def put_bucket_tagging(self, bucket_name, tagging):
|
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 = self.get_bucket(bucket_name)
|
||||||
bucket.set_tags(tagging)
|
bucket.set_tags(tagging)
|
||||||
|
|
||||||
|
@ -10,6 +10,7 @@ import xmltodict
|
|||||||
|
|
||||||
from moto.packages.httpretty.core import HTTPrettyRequest
|
from moto.packages.httpretty.core import HTTPrettyRequest
|
||||||
from moto.core.responses import _TemplateEnvironmentMixin
|
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, \
|
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
|
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):
|
if isinstance(request, HTTPrettyRequest):
|
||||||
path = request.path
|
path = request.path
|
||||||
else:
|
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):
|
if self.is_delete_keys(request, path, bucket_name):
|
||||||
return self._bucket_response_delete_keys(request, body, bucket_name, headers)
|
return self._bucket_response_delete_keys(request, body, bucket_name, headers)
|
||||||
@ -708,7 +709,10 @@ class ResponseObject(_TemplateEnvironmentMixin):
|
|||||||
# Copy key
|
# Copy key
|
||||||
# you can have a quoted ?version=abc with a version Id, so work on
|
# you can have a quoted ?version=abc with a version Id, so work on
|
||||||
# we need to parse the unquoted string first
|
# 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).\
|
src_bucket, src_key = unquote(src_key_parsed.path).\
|
||||||
lstrip("/").split("/", 1)
|
lstrip("/").split("/", 1)
|
||||||
src_version_id = parse_qs(src_key_parsed.query).get(
|
src_version_id = parse_qs(src_key_parsed.query).get(
|
||||||
|
@ -2,6 +2,7 @@ from __future__ import unicode_literals
|
|||||||
|
|
||||||
import time
|
import time
|
||||||
import json
|
import json
|
||||||
|
import uuid
|
||||||
|
|
||||||
import boto3
|
import boto3
|
||||||
|
|
||||||
@ -18,10 +19,6 @@ class SecretsManager(BaseModel):
|
|||||||
|
|
||||||
def __init__(self, region_name, **kwargs):
|
def __init__(self, region_name, **kwargs):
|
||||||
self.region = region_name
|
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):
|
class SecretsManagerBackend(BaseBackend):
|
||||||
@ -29,14 +26,7 @@ class SecretsManagerBackend(BaseBackend):
|
|||||||
def __init__(self, region_name=None, **kwargs):
|
def __init__(self, region_name=None, **kwargs):
|
||||||
super(SecretsManagerBackend, self).__init__()
|
super(SecretsManagerBackend, self).__init__()
|
||||||
self.region = region_name
|
self.region = region_name
|
||||||
self.secret_id = kwargs.get('secret_id', '')
|
self.secrets = {}
|
||||||
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 = ''
|
|
||||||
|
|
||||||
def reset(self):
|
def reset(self):
|
||||||
region_name = self.region
|
region_name = self.region
|
||||||
@ -44,36 +34,50 @@ class SecretsManagerBackend(BaseBackend):
|
|||||||
self.__init__(region_name)
|
self.__init__(region_name)
|
||||||
|
|
||||||
def _is_valid_identifier(self, identifier):
|
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):
|
def get_secret_value(self, secret_id, version_id, version_stage):
|
||||||
|
|
||||||
if not self._is_valid_identifier(secret_id):
|
if not self._is_valid_identifier(secret_id):
|
||||||
raise ResourceNotFoundException()
|
raise ResourceNotFoundException()
|
||||||
|
|
||||||
|
secret = self.secrets[secret_id]
|
||||||
|
|
||||||
response = json.dumps({
|
response = json.dumps({
|
||||||
"ARN": secret_arn(self.region, self.secret_id),
|
"ARN": secret_arn(self.region, secret['secret_id']),
|
||||||
"Name": self.name,
|
"Name": secret['name'],
|
||||||
"VersionId": "A435958A-D821-4193-B719-B7769357AER4",
|
"VersionId": secret['version_id'],
|
||||||
"SecretString": self.secret_string,
|
"SecretString": secret['secret_string'],
|
||||||
"VersionStages": [
|
"VersionStages": [
|
||||||
"AWSCURRENT",
|
"AWSCURRENT",
|
||||||
],
|
],
|
||||||
"CreatedDate": "2018-05-23 13:16:57.198000"
|
"CreatedDate": secret['createdate']
|
||||||
})
|
})
|
||||||
|
|
||||||
return response
|
return response
|
||||||
|
|
||||||
def create_secret(self, name, secret_string, **kwargs):
|
def create_secret(self, name, secret_string, tags, **kwargs):
|
||||||
|
|
||||||
self.secret_string = secret_string
|
generated_version_id = str(uuid.uuid4())
|
||||||
self.secret_id = name
|
|
||||||
self.name = name
|
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({
|
response = json.dumps({
|
||||||
"ARN": secret_arn(self.region, name),
|
"ARN": secret_arn(self.region, name),
|
||||||
"Name": self.name,
|
"Name": name,
|
||||||
"VersionId": "A435958A-D821-4193-B719-B7769357AER4",
|
"VersionId": generated_version_id,
|
||||||
})
|
})
|
||||||
|
|
||||||
return response
|
return response
|
||||||
@ -82,26 +86,23 @@ class SecretsManagerBackend(BaseBackend):
|
|||||||
if not self._is_valid_identifier(secret_id):
|
if not self._is_valid_identifier(secret_id):
|
||||||
raise ResourceNotFoundException
|
raise ResourceNotFoundException
|
||||||
|
|
||||||
|
secret = self.secrets[secret_id]
|
||||||
|
|
||||||
response = json.dumps({
|
response = json.dumps({
|
||||||
"ARN": secret_arn(self.region, self.secret_id),
|
"ARN": secret_arn(self.region, secret['secret_id']),
|
||||||
"Name": self.name,
|
"Name": secret['name'],
|
||||||
"Description": "",
|
"Description": "",
|
||||||
"KmsKeyId": "",
|
"KmsKeyId": "",
|
||||||
"RotationEnabled": self.rotation_enabled,
|
"RotationEnabled": secret['rotation_enabled'],
|
||||||
"RotationLambdaARN": self.rotation_lambda_arn,
|
"RotationLambdaARN": secret['rotation_lambda_arn'],
|
||||||
"RotationRules": {
|
"RotationRules": {
|
||||||
"AutomaticallyAfterDays": self.auto_rotate_after_days
|
"AutomaticallyAfterDays": secret['auto_rotate_after_days']
|
||||||
},
|
},
|
||||||
"LastRotatedDate": None,
|
"LastRotatedDate": None,
|
||||||
"LastChangedDate": None,
|
"LastChangedDate": None,
|
||||||
"LastAccessedDate": None,
|
"LastAccessedDate": None,
|
||||||
"DeletedDate": None,
|
"DeletedDate": None,
|
||||||
"Tags": [
|
"Tags": secret['tags']
|
||||||
{
|
|
||||||
"Key": "",
|
|
||||||
"Value": ""
|
|
||||||
},
|
|
||||||
]
|
|
||||||
})
|
})
|
||||||
|
|
||||||
return response
|
return response
|
||||||
@ -141,17 +142,19 @@ class SecretsManagerBackend(BaseBackend):
|
|||||||
)
|
)
|
||||||
raise InvalidParameterException(msg)
|
raise InvalidParameterException(msg)
|
||||||
|
|
||||||
self.version_id = client_request_token or ''
|
secret = self.secrets[secret_id]
|
||||||
self.rotation_lambda_arn = rotation_lambda_arn or ''
|
|
||||||
|
secret['version_id'] = client_request_token or ''
|
||||||
|
secret['rotation_lambda_arn'] = rotation_lambda_arn or ''
|
||||||
if rotation_rules:
|
if rotation_rules:
|
||||||
self.auto_rotate_after_days = rotation_rules.get(rotation_days, 0)
|
secret['auto_rotate_after_days'] = rotation_rules.get(rotation_days, 0)
|
||||||
if self.auto_rotate_after_days > 0:
|
if secret['auto_rotate_after_days'] > 0:
|
||||||
self.rotation_enabled = True
|
secret['rotation_enabled'] = True
|
||||||
|
|
||||||
response = json.dumps({
|
response = json.dumps({
|
||||||
"ARN": secret_arn(self.region, self.secret_id),
|
"ARN": secret_arn(self.region, secret['secret_id']),
|
||||||
"Name": self.name,
|
"Name": secret['name'],
|
||||||
"VersionId": self.version_id
|
"VersionId": secret['version_id']
|
||||||
})
|
})
|
||||||
|
|
||||||
return response
|
return response
|
||||||
|
@ -19,9 +19,11 @@ class SecretsManagerResponse(BaseResponse):
|
|||||||
def create_secret(self):
|
def create_secret(self):
|
||||||
name = self._get_param('Name')
|
name = self._get_param('Name')
|
||||||
secret_string = self._get_param('SecretString')
|
secret_string = self._get_param('SecretString')
|
||||||
|
tags = self._get_param('Tags', if_none=[])
|
||||||
return secretsmanager_backends[self.region].create_secret(
|
return secretsmanager_backends[self.region].create_secret(
|
||||||
name=name,
|
name=name,
|
||||||
secret_string=secret_string
|
secret_string=secret_string,
|
||||||
|
tags=tags
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_random_password(self):
|
def get_random_password(self):
|
||||||
|
@ -52,8 +52,9 @@ def random_password(password_length, exclude_characters, exclude_numbers,
|
|||||||
|
|
||||||
|
|
||||||
def secret_arn(region, secret_id):
|
def secret_arn(region, secret_id):
|
||||||
return "arn:aws:secretsmanager:{0}:1234567890:secret:{1}-rIjad".format(
|
id_string = ''.join(random.choice(string.ascii_letters) for _ in range(5))
|
||||||
region, secret_id)
|
return "arn:aws:secretsmanager:{0}:1234567890:secret:{1}-{2}".format(
|
||||||
|
region, secret_id, id_string)
|
||||||
|
|
||||||
|
|
||||||
def _exclude_characters(password, exclude_characters):
|
def _exclude_characters(password, exclude_characters):
|
||||||
|
@ -8,7 +8,7 @@ freezegun
|
|||||||
flask
|
flask
|
||||||
boto>=2.45.0
|
boto>=2.45.0
|
||||||
boto3>=1.4.4
|
boto3>=1.4.4
|
||||||
botocore>=1.8.36
|
botocore>=1.12.13
|
||||||
six>=1.9
|
six>=1.9
|
||||||
prompt-toolkit==1.0.14
|
prompt-toolkit==1.0.14
|
||||||
click==6.7
|
click==6.7
|
||||||
|
23
setup.py
23
setup.py
@ -1,15 +1,28 @@
|
|||||||
#!/usr/bin/env python
|
#!/usr/bin/env python
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
import codecs
|
||||||
|
import os
|
||||||
|
import re
|
||||||
import setuptools
|
import setuptools
|
||||||
from setuptools import setup, find_packages
|
from setuptools import setup, find_packages
|
||||||
import sys
|
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 = [
|
install_requires = [
|
||||||
"Jinja2>=2.7.3",
|
"Jinja2>=2.7.3",
|
||||||
"boto>=2.36.0",
|
"boto>=2.36.0",
|
||||||
"boto3>=1.6.16,<1.8",
|
"boto3>=1.6.16",
|
||||||
"botocore>=1.9.16,<1.11",
|
"botocore>=1.12.13",
|
||||||
"cryptography>=2.3.0",
|
"cryptography>=2.3.0",
|
||||||
"requests>=2.5",
|
"requests>=2.5",
|
||||||
"xmltodict",
|
"xmltodict",
|
||||||
@ -22,7 +35,7 @@ install_requires = [
|
|||||||
"mock",
|
"mock",
|
||||||
"docker>=2.5.1",
|
"docker>=2.5.1",
|
||||||
"jsondiff==1.1.2",
|
"jsondiff==1.1.2",
|
||||||
"aws-xray-sdk<0.96,>=0.93",
|
"aws-xray-sdk!=0.96,>=0.93",
|
||||||
"responses>=0.9.0",
|
"responses>=0.9.0",
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -40,9 +53,11 @@ else:
|
|||||||
|
|
||||||
setup(
|
setup(
|
||||||
name='moto',
|
name='moto',
|
||||||
version='1.3.6',
|
version='1.3.7',
|
||||||
description='A library that allows your python tests to easily'
|
description='A library that allows your python tests to easily'
|
||||||
' mock out the boto library',
|
' mock out the boto library',
|
||||||
|
long_description=read('README.md'),
|
||||||
|
long_description_content_type='text/markdown',
|
||||||
author='Steve Pulec',
|
author='Steve Pulec',
|
||||||
author_email='spulec@gmail.com',
|
author_email='spulec@gmail.com',
|
||||||
url='https://github.com/spulec/moto',
|
url='https://github.com/spulec/moto',
|
||||||
|
@ -41,6 +41,56 @@ def test_list_user_pools():
|
|||||||
result["UserPools"][0]["Name"].should.equal(name)
|
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
|
@mock_cognitoidp
|
||||||
def test_describe_user_pool():
|
def test_describe_user_pool():
|
||||||
conn = boto3.client("cognito-idp", "us-west-2")
|
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)
|
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
|
@mock_cognitoidp
|
||||||
def test_describe_user_pool_client():
|
def test_describe_user_pool_client():
|
||||||
conn = boto3.client("cognito-idp", "us-west-2")
|
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)
|
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
|
@mock_cognitoidp
|
||||||
def test_describe_identity_providers():
|
def test_describe_identity_providers():
|
||||||
conn = boto3.client("cognito-idp", "us-west-2")
|
conn = boto3.client("cognito-idp", "us-west-2")
|
||||||
@ -396,6 +584,62 @@ def test_list_users():
|
|||||||
result["Users"][0]["Username"].should.equal(username)
|
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
|
@mock_cognitoidp
|
||||||
def test_admin_disable_user():
|
def test_admin_disable_user():
|
||||||
conn = boto3.client("cognito-idp", "us-west-2")
|
conn = boto3.client("cognito-idp", "us-west-2")
|
||||||
|
@ -633,6 +633,21 @@ def test_delete_service():
|
|||||||
'arn:aws:ecs:us-east-1:012345678910:task-definition/test_ecs_task:1')
|
'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
|
@mock_ec2
|
||||||
@mock_ecs
|
@mock_ecs
|
||||||
|
@ -14,6 +14,19 @@ from nose.tools import raises
|
|||||||
from tests.helpers import requires_boto_gte
|
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()
|
@mock_iam_deprecated()
|
||||||
def test_get_all_server_certs():
|
def test_get_all_server_certs():
|
||||||
conn = boto.connect_iam()
|
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')
|
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()
|
@mock_iam_deprecated()
|
||||||
def test_remove_role_from_instance_profile():
|
def test_remove_role_from_instance_profile():
|
||||||
@ -700,10 +717,10 @@ def test_get_account_authorization_details():
|
|||||||
import json
|
import json
|
||||||
conn = boto3.client('iam', region_name='us-east-1')
|
conn = boto3.client('iam', region_name='us-east-1')
|
||||||
conn.create_role(RoleName="my-role", AssumeRolePolicyDocument="some policy", Path="/my-path/")
|
conn.create_role(RoleName="my-role", AssumeRolePolicyDocument="some policy", Path="/my-path/")
|
||||||
conn.create_user(Path='/', UserName='testCloudAuxUser')
|
conn.create_user(Path='/', UserName='testUser')
|
||||||
conn.create_group(Path='/', GroupName='testCloudAuxGroup')
|
conn.create_group(Path='/', GroupName='testGroup')
|
||||||
conn.create_policy(
|
conn.create_policy(
|
||||||
PolicyName='testCloudAuxPolicy',
|
PolicyName='testPolicy',
|
||||||
Path='/',
|
Path='/',
|
||||||
PolicyDocument=json.dumps({
|
PolicyDocument=json.dumps({
|
||||||
"Version": "2012-10-17",
|
"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'])
|
result = conn.get_account_authorization_details(Filter=['Role'])
|
||||||
len(result['RoleDetailList']) == 1
|
assert len(result['RoleDetailList']) == 1
|
||||||
len(result['UserDetailList']) == 0
|
assert len(result['UserDetailList']) == 0
|
||||||
len(result['GroupDetailList']) == 0
|
assert len(result['GroupDetailList']) == 0
|
||||||
len(result['Policies']) == 0
|
assert len(result['Policies']) == 0
|
||||||
|
assert len(result['RoleDetailList'][0]['InstanceProfileList']) == 1
|
||||||
|
|
||||||
result = conn.get_account_authorization_details(Filter=['User'])
|
result = conn.get_account_authorization_details(Filter=['User'])
|
||||||
len(result['RoleDetailList']) == 0
|
assert len(result['RoleDetailList']) == 0
|
||||||
len(result['UserDetailList']) == 1
|
assert len(result['UserDetailList']) == 1
|
||||||
len(result['GroupDetailList']) == 0
|
assert len(result['GroupDetailList']) == 0
|
||||||
len(result['Policies']) == 0
|
assert len(result['Policies']) == 0
|
||||||
|
|
||||||
result = conn.get_account_authorization_details(Filter=['Group'])
|
result = conn.get_account_authorization_details(Filter=['Group'])
|
||||||
len(result['RoleDetailList']) == 0
|
assert len(result['RoleDetailList']) == 0
|
||||||
len(result['UserDetailList']) == 0
|
assert len(result['UserDetailList']) == 0
|
||||||
len(result['GroupDetailList']) == 1
|
assert len(result['GroupDetailList']) == 1
|
||||||
len(result['Policies']) == 0
|
assert len(result['Policies']) == 0
|
||||||
|
|
||||||
result = conn.get_account_authorization_details(Filter=['LocalManagedPolicy'])
|
result = conn.get_account_authorization_details(Filter=['LocalManagedPolicy'])
|
||||||
len(result['RoleDetailList']) == 0
|
assert len(result['RoleDetailList']) == 0
|
||||||
len(result['UserDetailList']) == 0
|
assert len(result['UserDetailList']) == 0
|
||||||
len(result['GroupDetailList']) == 0
|
assert len(result['GroupDetailList']) == 0
|
||||||
len(result['Policies']) == 1
|
assert len(result['Policies']) == 1
|
||||||
|
|
||||||
# Check for greater than 1 since this should always be greater than one but might change.
|
# Check for greater than 1 since this should always be greater than one but might change.
|
||||||
# See iam/aws_managed_policies.py
|
# See iam/aws_managed_policies.py
|
||||||
result = conn.get_account_authorization_details(Filter=['AWSManagedPolicy'])
|
result = conn.get_account_authorization_details(Filter=['AWSManagedPolicy'])
|
||||||
len(result['RoleDetailList']) == 0
|
assert len(result['RoleDetailList']) == 0
|
||||||
len(result['UserDetailList']) == 0
|
assert len(result['UserDetailList']) == 0
|
||||||
len(result['GroupDetailList']) == 0
|
assert len(result['GroupDetailList']) == 0
|
||||||
len(result['Policies']) > 1
|
assert len(result['Policies']) > 1
|
||||||
|
|
||||||
result = conn.get_account_authorization_details()
|
result = conn.get_account_authorization_details()
|
||||||
len(result['RoleDetailList']) == 1
|
assert len(result['RoleDetailList']) == 1
|
||||||
len(result['UserDetailList']) == 1
|
assert len(result['UserDetailList']) == 1
|
||||||
len(result['GroupDetailList']) == 1
|
assert len(result['GroupDetailList']) == 1
|
||||||
len(result['Policies']) > 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']
|
||||||
|
@ -1553,6 +1553,24 @@ def test_boto3_put_bucket_tagging():
|
|||||||
})
|
})
|
||||||
resp['ResponseMetadata']['HTTPStatusCode'].should.equal(200)
|
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
|
@mock_s3
|
||||||
def test_boto3_get_bucket_tagging():
|
def test_boto3_get_bucket_tagging():
|
||||||
|
@ -39,12 +39,28 @@ def test_create_secret():
|
|||||||
conn = boto3.client('secretsmanager', region_name='us-east-1')
|
conn = boto3.client('secretsmanager', region_name='us-east-1')
|
||||||
|
|
||||||
result = conn.create_secret(Name='test-secret', SecretString="foosecret")
|
result = conn.create_secret(Name='test-secret', SecretString="foosecret")
|
||||||
assert result['ARN'] == (
|
assert result['ARN']
|
||||||
'arn:aws:secretsmanager:us-east-1:1234567890:secret:test-secret-rIjad')
|
|
||||||
assert result['Name'] == 'test-secret'
|
assert result['Name'] == 'test-secret'
|
||||||
secret = conn.get_secret_value(SecretId='test-secret')
|
secret = conn.get_secret_value(SecretId='test-secret')
|
||||||
assert secret['SecretString'] == 'foosecret'
|
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
|
@mock_secretsmanager
|
||||||
def test_get_random_password_default_length():
|
def test_get_random_password_default_length():
|
||||||
conn = boto3.client('secretsmanager', region_name='us-west-2')
|
conn = boto3.client('secretsmanager', region_name='us-west-2')
|
||||||
@ -159,10 +175,17 @@ def test_describe_secret():
|
|||||||
conn.create_secret(Name='test-secret',
|
conn.create_secret(Name='test-secret',
|
||||||
SecretString='foosecret')
|
SecretString='foosecret')
|
||||||
|
|
||||||
|
conn.create_secret(Name='test-secret-2',
|
||||||
|
SecretString='barsecret')
|
||||||
|
|
||||||
secret_description = conn.describe_secret(SecretId='test-secret')
|
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 # Returned dict is not empty
|
||||||
assert secret_description['ARN'] == (
|
assert secret_description['Name'] == ('test-secret')
|
||||||
'arn:aws:secretsmanager:us-west-2:1234567890:secret:test-secret-rIjad')
|
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
|
@mock_secretsmanager
|
||||||
def test_describe_secret_that_does_not_exist():
|
def test_describe_secret_that_does_not_exist():
|
||||||
@ -190,9 +213,7 @@ def test_rotate_secret():
|
|||||||
rotated_secret = conn.rotate_secret(SecretId=secret_name)
|
rotated_secret = conn.rotate_secret(SecretId=secret_name)
|
||||||
|
|
||||||
assert rotated_secret
|
assert rotated_secret
|
||||||
assert rotated_secret['ARN'] == (
|
assert rotated_secret['ARN'] != '' # Test arn not empty
|
||||||
'arn:aws:secretsmanager:us-west-2:1234567890:secret:test-secret-rIjad'
|
|
||||||
)
|
|
||||||
assert rotated_secret['Name'] == secret_name
|
assert rotated_secret['Name'] == secret_name
|
||||||
assert rotated_secret['VersionId'] != ''
|
assert rotated_secret['VersionId'] != ''
|
||||||
|
|
||||||
|
@ -82,12 +82,21 @@ def test_create_secret():
|
|||||||
headers={
|
headers={
|
||||||
"X-Amz-Target": "secretsmanager.CreateSecret"},
|
"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"))
|
json_data = json.loads(res.data.decode("utf-8"))
|
||||||
assert json_data['ARN'] == (
|
assert json_data['ARN'] != ''
|
||||||
'arn:aws:secretsmanager:us-east-1:1234567890:secret:test-secret-rIjad')
|
|
||||||
assert json_data['Name'] == 'test-secret'
|
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
|
@mock_secretsmanager
|
||||||
def test_describe_secret():
|
def test_describe_secret():
|
||||||
|
|
||||||
@ -108,11 +117,29 @@ def test_describe_secret():
|
|||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
||||||
|
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"))
|
json_data = json.loads(describe_secret.data.decode("utf-8"))
|
||||||
assert json_data # Returned dict is not empty
|
assert json_data # Returned dict is not empty
|
||||||
assert json_data['ARN'] == (
|
assert json_data['ARN'] != ''
|
||||||
'arn:aws:secretsmanager:us-east-1:1234567890:secret:test-secret-rIjad'
|
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
|
@mock_secretsmanager
|
||||||
def test_describe_secret_that_does_not_exist():
|
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"))
|
json_data = json.loads(rotate_secret.data.decode("utf-8"))
|
||||||
assert json_data # Returned dict is not empty
|
assert json_data # Returned dict is not empty
|
||||||
assert json_data['ARN'] == (
|
assert json_data['ARN'] != ''
|
||||||
'arn:aws:secretsmanager:us-east-1:1234567890:secret:test-secret-rIjad'
|
|
||||||
)
|
|
||||||
assert json_data['Name'] == 'test-secret'
|
assert json_data['Name'] == 'test-secret'
|
||||||
assert json_data['VersionId'] == client_request_token
|
assert json_data['VersionId'] == client_request_token
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user