Merge branch 'master' into add_instance_type_offerings
This commit is contained in:
commit
5d208bfd04
@ -3,6 +3,7 @@
|
|||||||
exclude_lines =
|
exclude_lines =
|
||||||
if __name__ == .__main__.:
|
if __name__ == .__main__.:
|
||||||
raise NotImplemented.
|
raise NotImplemented.
|
||||||
|
return NotImplemented
|
||||||
def __repr__
|
def __repr__
|
||||||
|
|
||||||
[run]
|
[run]
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
dist: bionic
|
dist: focal
|
||||||
language: python
|
language: python
|
||||||
services:
|
services:
|
||||||
- docker
|
- docker
|
||||||
@ -27,7 +27,7 @@ install:
|
|||||||
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:${PYTHON_DOCKER_TAG} /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:${PYTHON_DOCKER_TAG} /moto/travis_moto_server.sh &
|
||||||
fi
|
fi
|
||||||
travis_retry pip install -r requirements-dev.txt
|
travis_retry pip install -r requirements-dev.txt
|
||||||
travis_retry pip install "docker>=2.5.1,<=4.2.2" # Limit version due to old Docker Engine in Travis https://github.com/docker/docker-py/issues/2639
|
travis_retry pip install docker>=2.5.1
|
||||||
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
|
||||||
travis_retry pip install dist/moto*.gz
|
travis_retry pip install dist/moto*.gz
|
||||||
|
@ -8223,7 +8223,7 @@
|
|||||||
- [X] assume_role_with_web_identity
|
- [X] assume_role_with_web_identity
|
||||||
- [ ] decode_authorization_message
|
- [ ] decode_authorization_message
|
||||||
- [ ] get_access_key_info
|
- [ ] get_access_key_info
|
||||||
- [ ] get_caller_identity
|
- [x] get_caller_identity
|
||||||
- [X] get_federation_token
|
- [X] get_federation_token
|
||||||
- [X] get_session_token
|
- [X] get_session_token
|
||||||
</details>
|
</details>
|
||||||
|
2
Makefile
2
Makefile
@ -39,7 +39,7 @@ upload_pypi_artifact:
|
|||||||
twine upload dist/*
|
twine upload dist/*
|
||||||
|
|
||||||
push_dockerhub_image:
|
push_dockerhub_image:
|
||||||
docker build -t motoserver/moto .
|
docker build -t motoserver/moto . --tag moto:`python setup.py --version`
|
||||||
docker push motoserver/moto
|
docker push motoserver/moto
|
||||||
|
|
||||||
tag_github_release:
|
tag_github_release:
|
||||||
|
@ -2,7 +2,10 @@ from __future__ import unicode_literals
|
|||||||
|
|
||||||
import random
|
import random
|
||||||
|
|
||||||
from boto.ec2.blockdevicemapping import BlockDeviceType, BlockDeviceMapping
|
from moto.packages.boto.ec2.blockdevicemapping import (
|
||||||
|
BlockDeviceType,
|
||||||
|
BlockDeviceMapping,
|
||||||
|
)
|
||||||
from moto.ec2.exceptions import InvalidInstanceIdError
|
from moto.ec2.exceptions import InvalidInstanceIdError
|
||||||
|
|
||||||
from moto.compat import OrderedDict
|
from moto.compat import OrderedDict
|
||||||
|
@ -50,7 +50,7 @@ from .exceptions import (
|
|||||||
UnformattedGetAttTemplateException,
|
UnformattedGetAttTemplateException,
|
||||||
ValidationError,
|
ValidationError,
|
||||||
)
|
)
|
||||||
from boto.cloudformation.stack import Output
|
from moto.packages.boto.cloudformation.stack import Output
|
||||||
|
|
||||||
# List of supported CloudFormation models
|
# List of supported CloudFormation models
|
||||||
MODEL_LIST = CloudFormationModel.__subclasses__()
|
MODEL_LIST = CloudFormationModel.__subclasses__()
|
||||||
|
@ -1066,5 +1066,7 @@ def find_region_by_value(key, value):
|
|||||||
|
|
||||||
if key == "access_token" and value in user_pool.access_tokens:
|
if key == "access_token" and value in user_pool.access_tokens:
|
||||||
return region
|
return region
|
||||||
|
# If we can't find the `client_id` or `access_token`, we just pass
|
||||||
return cognitoidp_backends.keys()[0]
|
# back a default backend region, which will raise the appropriate
|
||||||
|
# error message (e.g. NotAuthorized or NotFound).
|
||||||
|
return list(cognitoidp_backends)[0]
|
||||||
|
@ -292,11 +292,19 @@ class SecondaryIndex(BaseModel):
|
|||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
if self.projection:
|
if self.projection:
|
||||||
if self.projection.get("ProjectionType", None) == "KEYS_ONLY":
|
projection_type = self.projection.get("ProjectionType", None)
|
||||||
allowed_attributes = ",".join(
|
key_attributes = self.table_key_attrs + [
|
||||||
self.table_key_attrs + [key["AttributeName"] for key in self.schema]
|
key["AttributeName"] for key in self.schema
|
||||||
|
]
|
||||||
|
|
||||||
|
if projection_type == "KEYS_ONLY":
|
||||||
|
item.filter(",".join(key_attributes))
|
||||||
|
elif projection_type == "INCLUDE":
|
||||||
|
allowed_attributes = key_attributes + self.projection.get(
|
||||||
|
"NonKeyAttributes", []
|
||||||
)
|
)
|
||||||
item.filter(allowed_attributes)
|
item.filter(",".join(allowed_attributes))
|
||||||
|
# ALL is handled implicitly by not filtering
|
||||||
return item
|
return item
|
||||||
|
|
||||||
|
|
||||||
|
@ -15,10 +15,15 @@ from pkg_resources import resource_filename
|
|||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
import weakref
|
import weakref
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from boto.ec2.instance import Instance as BotoInstance, Reservation
|
from moto.packages.boto.ec2.instance import Instance as BotoInstance, Reservation
|
||||||
from boto.ec2.blockdevicemapping import BlockDeviceMapping, BlockDeviceType
|
from moto.packages.boto.ec2.blockdevicemapping import (
|
||||||
from boto.ec2.spotinstancerequest import SpotInstanceRequest as BotoSpotRequest
|
BlockDeviceMapping,
|
||||||
from boto.ec2.launchspecification import LaunchSpecification
|
BlockDeviceType,
|
||||||
|
)
|
||||||
|
from moto.packages.boto.ec2.spotinstancerequest import (
|
||||||
|
SpotInstanceRequest as BotoSpotRequest,
|
||||||
|
)
|
||||||
|
from moto.packages.boto.ec2.launchspecification import LaunchSpecification
|
||||||
|
|
||||||
from moto.compat import OrderedDict
|
from moto.compat import OrderedDict
|
||||||
from moto.core import BaseBackend
|
from moto.core import BaseBackend
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import boto.ec2
|
from boto3 import Session
|
||||||
import json
|
import json
|
||||||
from moto.core import BaseBackend
|
from moto.core import BaseBackend
|
||||||
|
|
||||||
@ -11,5 +11,9 @@ class Ec2InstanceConnectBackend(BaseBackend):
|
|||||||
|
|
||||||
|
|
||||||
ec2instanceconnect_backends = {}
|
ec2instanceconnect_backends = {}
|
||||||
for region in boto.ec2.regions():
|
for region in Session().get_available_regions("ec2"):
|
||||||
ec2instanceconnect_backends[region.name] = Ec2InstanceConnectBackend()
|
ec2instanceconnect_backends[region] = Ec2InstanceConnectBackend()
|
||||||
|
for region in Session().get_available_regions("ec2", partition_name="aws-us-gov"):
|
||||||
|
ec2instanceconnect_backends[region] = Ec2InstanceConnectBackend()
|
||||||
|
for region in Session().get_available_regions("ec2", partition_name="aws-cn"):
|
||||||
|
ec2instanceconnect_backends[region] = Ec2InstanceConnectBackend()
|
||||||
|
@ -4,14 +4,14 @@ import datetime
|
|||||||
|
|
||||||
import pytz
|
import pytz
|
||||||
|
|
||||||
from boto.ec2.elb.attributes import (
|
from moto.packages.boto.ec2.elb.attributes import (
|
||||||
LbAttributes,
|
LbAttributes,
|
||||||
ConnectionSettingAttribute,
|
ConnectionSettingAttribute,
|
||||||
ConnectionDrainingAttribute,
|
ConnectionDrainingAttribute,
|
||||||
AccessLogAttribute,
|
AccessLogAttribute,
|
||||||
CrossZoneLoadBalancingAttribute,
|
CrossZoneLoadBalancingAttribute,
|
||||||
)
|
)
|
||||||
from boto.ec2.elb.policies import Policies, OtherPolicy
|
from moto.packages.boto.ec2.elb.policies import Policies, OtherPolicy
|
||||||
from moto.compat import OrderedDict
|
from moto.compat import OrderedDict
|
||||||
from moto.core import BaseBackend, BaseModel, CloudFormationModel
|
from moto.core import BaseBackend, BaseModel, CloudFormationModel
|
||||||
from moto.ec2.models import ec2_backends
|
from moto.ec2.models import ec2_backends
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
from boto.ec2.elb.attributes import (
|
from moto.packages.boto.ec2.elb.attributes import (
|
||||||
ConnectionSettingAttribute,
|
ConnectionSettingAttribute,
|
||||||
ConnectionDrainingAttribute,
|
ConnectionDrainingAttribute,
|
||||||
AccessLogAttribute,
|
AccessLogAttribute,
|
||||||
CrossZoneLoadBalancingAttribute,
|
CrossZoneLoadBalancingAttribute,
|
||||||
)
|
)
|
||||||
from boto.ec2.elb.policies import AppCookieStickinessPolicy, OtherPolicy
|
from moto.packages.boto.ec2.elb.policies import AppCookieStickinessPolicy, OtherPolicy
|
||||||
|
|
||||||
from moto.core.responses import BaseResponse
|
from moto.core.responses import BaseResponse
|
||||||
from .models import elb_backends
|
from .models import elb_backends
|
||||||
|
@ -13,7 +13,7 @@ from moto.core.responses import xml_to_json_response
|
|||||||
from moto.core.utils import tags_from_query_string
|
from moto.core.utils import tags_from_query_string
|
||||||
from .exceptions import EmrError
|
from .exceptions import EmrError
|
||||||
from .models import emr_backends
|
from .models import emr_backends
|
||||||
from .utils import steps_from_query_string, Unflattener
|
from .utils import steps_from_query_string, Unflattener, ReleaseLabel
|
||||||
|
|
||||||
|
|
||||||
def generate_boto3_response(operation):
|
def generate_boto3_response(operation):
|
||||||
@ -323,7 +323,9 @@ class ElasticMapReduceResponse(BaseResponse):
|
|||||||
custom_ami_id = self._get_param("CustomAmiId")
|
custom_ami_id = self._get_param("CustomAmiId")
|
||||||
if custom_ami_id:
|
if custom_ami_id:
|
||||||
kwargs["custom_ami_id"] = custom_ami_id
|
kwargs["custom_ami_id"] = custom_ami_id
|
||||||
if release_label and release_label < "emr-5.7.0":
|
if release_label and (
|
||||||
|
ReleaseLabel(release_label) < ReleaseLabel("emr-5.7.0")
|
||||||
|
):
|
||||||
message = "Custom AMI is not allowed"
|
message = "Custom AMI is not allowed"
|
||||||
raise EmrError(
|
raise EmrError(
|
||||||
error_type="ValidationException",
|
error_type="ValidationException",
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
import random
|
import random
|
||||||
|
import re
|
||||||
import string
|
import string
|
||||||
from moto.core.utils import camelcase_to_underscores
|
from moto.core.utils import camelcase_to_underscores
|
||||||
|
|
||||||
@ -144,3 +145,76 @@ class CamelToUnderscoresWalker:
|
|||||||
@staticmethod
|
@staticmethod
|
||||||
def parse_scalar(x):
|
def parse_scalar(x):
|
||||||
return x
|
return x
|
||||||
|
|
||||||
|
|
||||||
|
class ReleaseLabel(object):
|
||||||
|
|
||||||
|
version_re = re.compile(r"^emr-(\d+)\.(\d+)\.(\d+)$")
|
||||||
|
|
||||||
|
def __init__(self, release_label):
|
||||||
|
major, minor, patch = self.parse(release_label)
|
||||||
|
|
||||||
|
self.major = major
|
||||||
|
self.minor = minor
|
||||||
|
self.patch = patch
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def parse(cls, release_label):
|
||||||
|
if not release_label:
|
||||||
|
raise ValueError("Invalid empty ReleaseLabel: %r" % release_label)
|
||||||
|
|
||||||
|
match = cls.version_re.match(release_label)
|
||||||
|
if not match:
|
||||||
|
raise ValueError("Invalid ReleaseLabel: %r" % release_label)
|
||||||
|
|
||||||
|
major, minor, patch = match.groups()
|
||||||
|
|
||||||
|
major = int(major)
|
||||||
|
minor = int(minor)
|
||||||
|
patch = int(patch)
|
||||||
|
|
||||||
|
return major, minor, patch
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
version = "emr-%d.%d.%d" % (self.major, self.minor, self.patch)
|
||||||
|
return version
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return "%s(%r)" % (self.__class__.__name__, str(self))
|
||||||
|
|
||||||
|
def __iter__(self):
|
||||||
|
return iter((self.major, self.minor, self.patch))
|
||||||
|
|
||||||
|
def __eq__(self, other):
|
||||||
|
if not isinstance(other, self.__class__):
|
||||||
|
return NotImplemented
|
||||||
|
return (
|
||||||
|
self.major == other.major
|
||||||
|
and self.minor == other.minor
|
||||||
|
and self.patch == other.patch
|
||||||
|
)
|
||||||
|
|
||||||
|
def __ne__(self, other):
|
||||||
|
if not isinstance(other, self.__class__):
|
||||||
|
return NotImplemented
|
||||||
|
return tuple(self) != tuple(other)
|
||||||
|
|
||||||
|
def __lt__(self, other):
|
||||||
|
if not isinstance(other, self.__class__):
|
||||||
|
return NotImplemented
|
||||||
|
return tuple(self) < tuple(other)
|
||||||
|
|
||||||
|
def __le__(self, other):
|
||||||
|
if not isinstance(other, self.__class__):
|
||||||
|
return NotImplemented
|
||||||
|
return tuple(self) <= tuple(other)
|
||||||
|
|
||||||
|
def __gt__(self, other):
|
||||||
|
if not isinstance(other, self.__class__):
|
||||||
|
return NotImplemented
|
||||||
|
return tuple(self) > tuple(other)
|
||||||
|
|
||||||
|
def __ge__(self, other):
|
||||||
|
if not isinstance(other, self.__class__):
|
||||||
|
return NotImplemented
|
||||||
|
return tuple(self) >= tuple(other)
|
||||||
|
18
moto/packages/boto/README.md
Normal file
18
moto/packages/boto/README.md
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
## Removing the `boto` Dependency
|
||||||
|
|
||||||
|
In order to rid `moto` of a direct dependency on the long-deprecated `boto`
|
||||||
|
package, a subset of the `boto` code has been vendored here.
|
||||||
|
|
||||||
|
This directory contains only the `boto` files required for `moto` to run,
|
||||||
|
which is a very small subset of the original package's contents. Furthermore,
|
||||||
|
the `boto` models collected here have been stripped of all superfluous
|
||||||
|
methods/attributes not used by `moto`. (Any copyright headers on the
|
||||||
|
original files have been left intact.)
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
|
||||||
|
Currently, a small number of `moto` models inherit from these `boto` classes.
|
||||||
|
With some additional work, the inheritance can be dropped in favor of simply
|
||||||
|
adding the required methods/properties from these `boto` models to their
|
||||||
|
respective `moto` subclasses, which would allow for these files/directories
|
||||||
|
to be removed entirely.
|
0
moto/packages/boto/__init__.py
Normal file
0
moto/packages/boto/__init__.py
Normal file
0
moto/packages/boto/cloudformation/__init__.py
Normal file
0
moto/packages/boto/cloudformation/__init__.py
Normal file
9
moto/packages/boto/cloudformation/stack.py
Normal file
9
moto/packages/boto/cloudformation/stack.py
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
class Output(object):
|
||||||
|
def __init__(self, connection=None):
|
||||||
|
self.connection = connection
|
||||||
|
self.description = None
|
||||||
|
self.key = None
|
||||||
|
self.value = None
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return 'Output:"%s"="%s"' % (self.key, self.value)
|
0
moto/packages/boto/ec2/__init__.py
Normal file
0
moto/packages/boto/ec2/__init__.py
Normal file
83
moto/packages/boto/ec2/blockdevicemapping.py
Normal file
83
moto/packages/boto/ec2/blockdevicemapping.py
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
# Copyright (c) 2009-2012 Mitch Garnaat http://garnaat.org/
|
||||||
|
# Copyright (c) 2012 Amazon.com, Inc. or its affiliates. All Rights Reserved
|
||||||
|
#
|
||||||
|
# Permission is hereby granted, free of charge, to any person obtaining a
|
||||||
|
# copy of this software and associated documentation files (the
|
||||||
|
# "Software"), to deal in the Software without restriction, including
|
||||||
|
# without limitation the rights to use, copy, modify, merge, publish, dis-
|
||||||
|
# tribute, sublicense, and/or sell copies of the Software, and to permit
|
||||||
|
# persons to whom the Software is furnished to do so, subject to the fol-
|
||||||
|
# lowing conditions:
|
||||||
|
#
|
||||||
|
# The above copyright notice and this permission notice shall be included
|
||||||
|
# in all copies or substantial portions of the Software.
|
||||||
|
#
|
||||||
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||||
|
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
|
||||||
|
# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||||
|
# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||||
|
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||||
|
# IN THE SOFTWARE.
|
||||||
|
#
|
||||||
|
|
||||||
|
|
||||||
|
class BlockDeviceType(object):
|
||||||
|
"""
|
||||||
|
Represents parameters for a block device.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
connection=None,
|
||||||
|
ephemeral_name=None,
|
||||||
|
no_device=False,
|
||||||
|
volume_id=None,
|
||||||
|
snapshot_id=None,
|
||||||
|
status=None,
|
||||||
|
attach_time=None,
|
||||||
|
delete_on_termination=False,
|
||||||
|
size=None,
|
||||||
|
volume_type=None,
|
||||||
|
iops=None,
|
||||||
|
encrypted=None,
|
||||||
|
):
|
||||||
|
self.connection = connection
|
||||||
|
self.ephemeral_name = ephemeral_name
|
||||||
|
self.no_device = no_device
|
||||||
|
self.volume_id = volume_id
|
||||||
|
self.snapshot_id = snapshot_id
|
||||||
|
self.status = status
|
||||||
|
self.attach_time = attach_time
|
||||||
|
self.delete_on_termination = delete_on_termination
|
||||||
|
self.size = size
|
||||||
|
self.volume_type = volume_type
|
||||||
|
self.iops = iops
|
||||||
|
self.encrypted = encrypted
|
||||||
|
|
||||||
|
|
||||||
|
# for backwards compatibility
|
||||||
|
EBSBlockDeviceType = BlockDeviceType
|
||||||
|
|
||||||
|
|
||||||
|
class BlockDeviceMapping(dict):
|
||||||
|
"""
|
||||||
|
Represents a collection of BlockDeviceTypes when creating ec2 instances.
|
||||||
|
|
||||||
|
Example:
|
||||||
|
dev_sda1 = BlockDeviceType()
|
||||||
|
dev_sda1.size = 100 # change root volume to 100GB instead of default
|
||||||
|
bdm = BlockDeviceMapping()
|
||||||
|
bdm['/dev/sda1'] = dev_sda1
|
||||||
|
reservation = image.run(..., block_device_map=bdm, ...)
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, connection=None):
|
||||||
|
"""
|
||||||
|
:type connection: :class:`boto.ec2.EC2Connection`
|
||||||
|
:param connection: Optional connection.
|
||||||
|
"""
|
||||||
|
dict.__init__(self)
|
||||||
|
self.connection = connection
|
||||||
|
self.current_name = None
|
||||||
|
self.current_value = None
|
48
moto/packages/boto/ec2/ec2object.py
Normal file
48
moto/packages/boto/ec2/ec2object.py
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
# Copyright (c) 2006-2010 Mitch Garnaat http://garnaat.org/
|
||||||
|
# Copyright (c) 2010, Eucalyptus Systems, Inc.
|
||||||
|
#
|
||||||
|
# Permission is hereby granted, free of charge, to any person obtaining a
|
||||||
|
# copy of this software and associated documentation files (the
|
||||||
|
# "Software"), to deal in the Software without restriction, including
|
||||||
|
# without limitation the rights to use, copy, modify, merge, publish, dis-
|
||||||
|
# tribute, sublicense, and/or sell copies of the Software, and to permit
|
||||||
|
# persons to whom the Software is furnished to do so, subject to the fol-
|
||||||
|
# lowing conditions:
|
||||||
|
#
|
||||||
|
# The above copyright notice and this permission notice shall be included
|
||||||
|
# in all copies or substantial portions of the Software.
|
||||||
|
#
|
||||||
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||||
|
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
|
||||||
|
# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||||
|
# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||||
|
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||||
|
# IN THE SOFTWARE.
|
||||||
|
|
||||||
|
"""
|
||||||
|
Represents an EC2 Object
|
||||||
|
"""
|
||||||
|
from moto.packages.boto.ec2.tag import TagSet
|
||||||
|
|
||||||
|
|
||||||
|
class EC2Object(object):
|
||||||
|
def __init__(self, connection=None):
|
||||||
|
self.connection = connection
|
||||||
|
self.region = None
|
||||||
|
|
||||||
|
|
||||||
|
class TaggedEC2Object(EC2Object):
|
||||||
|
"""
|
||||||
|
Any EC2 resource that can be tagged should be represented
|
||||||
|
by a Python object that subclasses this class. This class
|
||||||
|
has the mechanism in place to handle the tagSet element in
|
||||||
|
the Describe* responses. If tags are found, it will create
|
||||||
|
a TagSet object and allow it to parse and collect the tags
|
||||||
|
into a dict that is stored in the "tags" attribute of the
|
||||||
|
object.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, connection=None):
|
||||||
|
super(TaggedEC2Object, self).__init__(connection)
|
||||||
|
self.tags = TagSet()
|
0
moto/packages/boto/ec2/elb/__init__.py
Normal file
0
moto/packages/boto/ec2/elb/__init__.py
Normal file
100
moto/packages/boto/ec2/elb/attributes.py
Normal file
100
moto/packages/boto/ec2/elb/attributes.py
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
# Permission is hereby granted, free of charge, to any person obtaining a
|
||||||
|
# copy of this software and associated documentation files (the
|
||||||
|
# "Software"), to deal in the Software without restriction, including
|
||||||
|
# without limitation the rights to use, copy, modify, merge, publish, dis-
|
||||||
|
# tribute, sublicense, and/or sell copies of the Software, and to permit
|
||||||
|
# persons to whom the Software is furnished to do so, subject to the fol-
|
||||||
|
# lowing conditions:
|
||||||
|
#
|
||||||
|
# The above copyright notice and this permission notice shall be included
|
||||||
|
# in all copies or substantial portions of the Software.
|
||||||
|
#
|
||||||
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||||
|
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
|
||||||
|
# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||||
|
# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||||
|
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||||
|
# IN THE SOFTWARE.
|
||||||
|
#
|
||||||
|
# Created by Chris Huegle for TellApart, Inc.
|
||||||
|
|
||||||
|
|
||||||
|
class ConnectionSettingAttribute(object):
|
||||||
|
"""
|
||||||
|
Represents the ConnectionSetting segment of ELB Attributes.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, connection=None):
|
||||||
|
self.idle_timeout = None
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return "ConnectionSettingAttribute(%s)" % (self.idle_timeout)
|
||||||
|
|
||||||
|
|
||||||
|
class CrossZoneLoadBalancingAttribute(object):
|
||||||
|
"""
|
||||||
|
Represents the CrossZoneLoadBalancing segement of ELB Attributes.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, connection=None):
|
||||||
|
self.enabled = None
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return "CrossZoneLoadBalancingAttribute(%s)" % (self.enabled)
|
||||||
|
|
||||||
|
|
||||||
|
class AccessLogAttribute(object):
|
||||||
|
"""
|
||||||
|
Represents the AccessLog segment of ELB attributes.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, connection=None):
|
||||||
|
self.enabled = None
|
||||||
|
self.s3_bucket_name = None
|
||||||
|
self.s3_bucket_prefix = None
|
||||||
|
self.emit_interval = None
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return "AccessLog(%s, %s, %s, %s)" % (
|
||||||
|
self.enabled,
|
||||||
|
self.s3_bucket_name,
|
||||||
|
self.s3_bucket_prefix,
|
||||||
|
self.emit_interval,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class ConnectionDrainingAttribute(object):
|
||||||
|
"""
|
||||||
|
Represents the ConnectionDraining segment of ELB attributes.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, connection=None):
|
||||||
|
self.enabled = None
|
||||||
|
self.timeout = None
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return "ConnectionDraining(%s, %s)" % (self.enabled, self.timeout)
|
||||||
|
|
||||||
|
|
||||||
|
class LbAttributes(object):
|
||||||
|
"""
|
||||||
|
Represents the Attributes of an Elastic Load Balancer.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, connection=None):
|
||||||
|
self.connection = connection
|
||||||
|
self.cross_zone_load_balancing = CrossZoneLoadBalancingAttribute(
|
||||||
|
self.connection
|
||||||
|
)
|
||||||
|
self.access_log = AccessLogAttribute(self.connection)
|
||||||
|
self.connection_draining = ConnectionDrainingAttribute(self.connection)
|
||||||
|
self.connecting_settings = ConnectionSettingAttribute(self.connection)
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return "LbAttributes(%s, %s, %s, %s)" % (
|
||||||
|
repr(self.cross_zone_load_balancing),
|
||||||
|
repr(self.access_log),
|
||||||
|
repr(self.connection_draining),
|
||||||
|
repr(self.connecting_settings),
|
||||||
|
)
|
55
moto/packages/boto/ec2/elb/policies.py
Normal file
55
moto/packages/boto/ec2/elb/policies.py
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
# Copyright (c) 2010 Reza Lotun http://reza.lotun.name
|
||||||
|
#
|
||||||
|
# Permission is hereby granted, free of charge, to any person obtaining a
|
||||||
|
# copy of this software and associated documentation files (the
|
||||||
|
# "Software"), to deal in the Software without restriction, including
|
||||||
|
# without limitation the rights to use, copy, modify, merge, publish, dis-
|
||||||
|
# tribute, sublicense, and/or sell copies of the Software, and to permit
|
||||||
|
# persons to whom the Software is furnished to do so, subject to the fol-
|
||||||
|
# lowing conditions:
|
||||||
|
#
|
||||||
|
# The above copyright notice and this permission notice shall be included
|
||||||
|
# in all copies or substantial portions of the Software.
|
||||||
|
#
|
||||||
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||||
|
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
|
||||||
|
# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||||
|
# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||||
|
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||||
|
# IN THE SOFTWARE.
|
||||||
|
|
||||||
|
|
||||||
|
class AppCookieStickinessPolicy(object):
|
||||||
|
def __init__(self, connection=None):
|
||||||
|
self.cookie_name = None
|
||||||
|
self.policy_name = None
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return "AppCookieStickiness(%s, %s)" % (self.policy_name, self.cookie_name)
|
||||||
|
|
||||||
|
|
||||||
|
class OtherPolicy(object):
|
||||||
|
def __init__(self, connection=None):
|
||||||
|
self.policy_name = None
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return "OtherPolicy(%s)" % (self.policy_name)
|
||||||
|
|
||||||
|
|
||||||
|
class Policies(object):
|
||||||
|
"""
|
||||||
|
ELB Policies
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, connection=None):
|
||||||
|
self.connection = connection
|
||||||
|
self.app_cookie_stickiness_policies = None
|
||||||
|
self.lb_cookie_stickiness_policies = None
|
||||||
|
self.other_policies = None
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
app = "AppCookieStickiness%s" % self.app_cookie_stickiness_policies
|
||||||
|
lb = "LBCookieStickiness%s" % self.lb_cookie_stickiness_policies
|
||||||
|
other = "Other%s" % self.other_policies
|
||||||
|
return "Policies(%s,%s,%s)" % (app, lb, other)
|
25
moto/packages/boto/ec2/image.py
Normal file
25
moto/packages/boto/ec2/image.py
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
# Copyright (c) 2006-2010 Mitch Garnaat http://garnaat.org/
|
||||||
|
# Copyright (c) 2010, Eucalyptus Systems, Inc.
|
||||||
|
#
|
||||||
|
# Permission is hereby granted, free of charge, to any person obtaining a
|
||||||
|
# copy of this software and associated documentation files (the
|
||||||
|
# "Software"), to deal in the Software without restriction, including
|
||||||
|
# without limitation the rights to use, copy, modify, merge, publish, dis-
|
||||||
|
# tribute, sublicense, and/or sell copies of the Software, and to permit
|
||||||
|
# persons to whom the Software is furnished to do so, subject to the fol-
|
||||||
|
# lowing conditions:
|
||||||
|
#
|
||||||
|
# The above copyright notice and this permission notice shall be included
|
||||||
|
# in all copies or substantial portions of the Software.
|
||||||
|
#
|
||||||
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||||
|
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
|
||||||
|
# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||||
|
# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||||
|
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||||
|
# IN THE SOFTWARE.
|
||||||
|
|
||||||
|
|
||||||
|
class ProductCodes(list):
|
||||||
|
pass
|
217
moto/packages/boto/ec2/instance.py
Normal file
217
moto/packages/boto/ec2/instance.py
Normal file
@ -0,0 +1,217 @@
|
|||||||
|
# Copyright (c) 2006-2012 Mitch Garnaat http://garnaat.org/
|
||||||
|
# Copyright (c) 2010, Eucalyptus Systems, Inc.
|
||||||
|
# Copyright (c) 2012 Amazon.com, Inc. or its affiliates. All Rights Reserved
|
||||||
|
#
|
||||||
|
# Permission is hereby granted, free of charge, to any person obtaining a
|
||||||
|
# copy of this software and associated documentation files (the
|
||||||
|
# "Software"), to deal in the Software without restriction, including
|
||||||
|
# without limitation the rights to use, copy, modify, merge, publish, dis-
|
||||||
|
# tribute, sublicense, and/or sell copies of the Software, and to permit
|
||||||
|
# persons to whom the Software is furnished to do so, subject to the fol-
|
||||||
|
# lowing conditions:
|
||||||
|
#
|
||||||
|
# The above copyright notice and this permission notice shall be included
|
||||||
|
# in all copies or substantial portions of the Software.
|
||||||
|
#
|
||||||
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||||
|
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
|
||||||
|
# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||||
|
# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||||
|
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||||
|
# IN THE SOFTWARE.
|
||||||
|
|
||||||
|
"""
|
||||||
|
Represents an EC2 Instance
|
||||||
|
"""
|
||||||
|
from moto.packages.boto.ec2.ec2object import EC2Object, TaggedEC2Object
|
||||||
|
from moto.packages.boto.ec2.image import ProductCodes
|
||||||
|
|
||||||
|
|
||||||
|
class InstanceState(object):
|
||||||
|
"""
|
||||||
|
The state of the instance.
|
||||||
|
|
||||||
|
:ivar code: The low byte represents the state. The high byte is an
|
||||||
|
opaque internal value and should be ignored. Valid values:
|
||||||
|
|
||||||
|
* 0 (pending)
|
||||||
|
* 16 (running)
|
||||||
|
* 32 (shutting-down)
|
||||||
|
* 48 (terminated)
|
||||||
|
* 64 (stopping)
|
||||||
|
* 80 (stopped)
|
||||||
|
|
||||||
|
:ivar name: The name of the state of the instance. Valid values:
|
||||||
|
|
||||||
|
* "pending"
|
||||||
|
* "running"
|
||||||
|
* "shutting-down"
|
||||||
|
* "terminated"
|
||||||
|
* "stopping"
|
||||||
|
* "stopped"
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, code=0, name=None):
|
||||||
|
self.code = code
|
||||||
|
self.name = name
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return "%s(%d)" % (self.name, self.code)
|
||||||
|
|
||||||
|
|
||||||
|
class InstancePlacement(object):
|
||||||
|
"""
|
||||||
|
The location where the instance launched.
|
||||||
|
|
||||||
|
:ivar zone: The Availability Zone of the instance.
|
||||||
|
:ivar group_name: The name of the placement group the instance is
|
||||||
|
in (for cluster compute instances).
|
||||||
|
:ivar tenancy: The tenancy of the instance (if the instance is
|
||||||
|
running within a VPC). An instance with a tenancy of dedicated
|
||||||
|
runs on single-tenant hardware.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, zone=None, group_name=None, tenancy=None):
|
||||||
|
self.zone = zone
|
||||||
|
self.group_name = group_name
|
||||||
|
self.tenancy = tenancy
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return self.zone
|
||||||
|
|
||||||
|
|
||||||
|
class Reservation(EC2Object):
|
||||||
|
"""
|
||||||
|
Represents a Reservation response object.
|
||||||
|
|
||||||
|
:ivar id: The unique ID of the Reservation.
|
||||||
|
:ivar owner_id: The unique ID of the owner of the Reservation.
|
||||||
|
:ivar groups: A list of Group objects representing the security
|
||||||
|
groups associated with launched instances.
|
||||||
|
:ivar instances: A list of Instance objects launched in this
|
||||||
|
Reservation.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, connection=None):
|
||||||
|
super(Reservation, self).__init__(connection)
|
||||||
|
self.id = None
|
||||||
|
self.owner_id = None
|
||||||
|
self.groups = []
|
||||||
|
self.instances = []
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return "Reservation:%s" % self.id
|
||||||
|
|
||||||
|
|
||||||
|
class Instance(TaggedEC2Object):
|
||||||
|
"""
|
||||||
|
Represents an instance.
|
||||||
|
|
||||||
|
:ivar id: The unique ID of the Instance.
|
||||||
|
:ivar groups: A list of Group objects representing the security
|
||||||
|
groups associated with the instance.
|
||||||
|
:ivar public_dns_name: The public dns name of the instance.
|
||||||
|
:ivar private_dns_name: The private dns name of the instance.
|
||||||
|
:ivar state: The string representation of the instance's current state.
|
||||||
|
:ivar state_code: An integer representation of the instance's
|
||||||
|
current state.
|
||||||
|
:ivar previous_state: The string representation of the instance's
|
||||||
|
previous state.
|
||||||
|
:ivar previous_state_code: An integer representation of the
|
||||||
|
instance's current state.
|
||||||
|
:ivar key_name: The name of the SSH key associated with the instance.
|
||||||
|
:ivar instance_type: The type of instance (e.g. m1.small).
|
||||||
|
:ivar launch_time: The time the instance was launched.
|
||||||
|
:ivar image_id: The ID of the AMI used to launch this instance.
|
||||||
|
:ivar placement: The availability zone in which the instance is running.
|
||||||
|
:ivar placement_group: The name of the placement group the instance
|
||||||
|
is in (for cluster compute instances).
|
||||||
|
:ivar placement_tenancy: The tenancy of the instance, if the instance
|
||||||
|
is running within a VPC. An instance with a tenancy of dedicated
|
||||||
|
runs on a single-tenant hardware.
|
||||||
|
:ivar kernel: The kernel associated with the instance.
|
||||||
|
:ivar ramdisk: The ramdisk associated with the instance.
|
||||||
|
:ivar architecture: The architecture of the image (i386|x86_64).
|
||||||
|
:ivar hypervisor: The hypervisor used.
|
||||||
|
:ivar virtualization_type: The type of virtualization used.
|
||||||
|
:ivar product_codes: A list of product codes associated with this instance.
|
||||||
|
:ivar ami_launch_index: This instances position within it's launch group.
|
||||||
|
:ivar monitored: A boolean indicating whether monitoring is enabled or not.
|
||||||
|
:ivar monitoring_state: A string value that contains the actual value
|
||||||
|
of the monitoring element returned by EC2.
|
||||||
|
:ivar spot_instance_request_id: The ID of the spot instance request
|
||||||
|
if this is a spot instance.
|
||||||
|
:ivar subnet_id: The VPC Subnet ID, if running in VPC.
|
||||||
|
:ivar vpc_id: The VPC ID, if running in VPC.
|
||||||
|
:ivar private_ip_address: The private IP address of the instance.
|
||||||
|
:ivar ip_address: The public IP address of the instance.
|
||||||
|
:ivar platform: Platform of the instance (e.g. Windows)
|
||||||
|
:ivar root_device_name: The name of the root device.
|
||||||
|
:ivar root_device_type: The root device type (ebs|instance-store).
|
||||||
|
:ivar block_device_mapping: The Block Device Mapping for the instance.
|
||||||
|
:ivar state_reason: The reason for the most recent state transition.
|
||||||
|
:ivar interfaces: List of Elastic Network Interfaces associated with
|
||||||
|
this instance.
|
||||||
|
:ivar ebs_optimized: Whether instance is using optimized EBS volumes
|
||||||
|
or not.
|
||||||
|
:ivar instance_profile: A Python dict containing the instance
|
||||||
|
profile id and arn associated with this instance.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, connection=None):
|
||||||
|
super(Instance, self).__init__(connection)
|
||||||
|
self.id = None
|
||||||
|
self.dns_name = None
|
||||||
|
self.public_dns_name = None
|
||||||
|
self.private_dns_name = None
|
||||||
|
self.key_name = None
|
||||||
|
self.instance_type = None
|
||||||
|
self.launch_time = None
|
||||||
|
self.image_id = None
|
||||||
|
self.kernel = None
|
||||||
|
self.ramdisk = None
|
||||||
|
self.product_codes = ProductCodes()
|
||||||
|
self.ami_launch_index = None
|
||||||
|
self.monitored = False
|
||||||
|
self.monitoring_state = None
|
||||||
|
self.spot_instance_request_id = None
|
||||||
|
self.subnet_id = None
|
||||||
|
self.vpc_id = None
|
||||||
|
self.private_ip_address = None
|
||||||
|
self.ip_address = None
|
||||||
|
self.requester_id = None
|
||||||
|
self._in_monitoring_element = False
|
||||||
|
self.persistent = False
|
||||||
|
self.root_device_name = None
|
||||||
|
self.root_device_type = None
|
||||||
|
self.block_device_mapping = None
|
||||||
|
self.state_reason = None
|
||||||
|
self.group_name = None
|
||||||
|
self.client_token = None
|
||||||
|
self.eventsSet = None
|
||||||
|
self.groups = []
|
||||||
|
self.platform = None
|
||||||
|
self.interfaces = []
|
||||||
|
self.hypervisor = None
|
||||||
|
self.virtualization_type = None
|
||||||
|
self.architecture = None
|
||||||
|
self.instance_profile = None
|
||||||
|
self._previous_state = None
|
||||||
|
self._state = InstanceState()
|
||||||
|
self._placement = InstancePlacement()
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return "Instance:%s" % self.id
|
||||||
|
|
||||||
|
@property
|
||||||
|
def state(self):
|
||||||
|
return self._state.name
|
||||||
|
|
||||||
|
@property
|
||||||
|
def state_code(self):
|
||||||
|
return self._state.code
|
||||||
|
|
||||||
|
@property
|
||||||
|
def placement(self):
|
||||||
|
return self._placement.zone
|
50
moto/packages/boto/ec2/instancetype.py
Normal file
50
moto/packages/boto/ec2/instancetype.py
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
# Copyright (c) 2006-2009 Mitch Garnaat http://garnaat.org/
|
||||||
|
#
|
||||||
|
# Permission is hereby granted, free of charge, to any person obtaining a
|
||||||
|
# copy of this software and associated documentation files (the
|
||||||
|
# "Software"), to deal in the Software without restriction, including
|
||||||
|
# without limitation the rights to use, copy, modify, merge, publish, dis-
|
||||||
|
# tribute, sublicense, and/or sell copies of the Software, and to permit
|
||||||
|
# persons to whom the Software is furnished to do so, subject to the fol-
|
||||||
|
# lowing conditions:
|
||||||
|
#
|
||||||
|
# The above copyright notice and this permission notice shall be included
|
||||||
|
# in all copies or substantial portions of the Software.
|
||||||
|
#
|
||||||
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||||
|
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
|
||||||
|
# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||||
|
# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||||
|
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||||
|
# IN THE SOFTWARE.
|
||||||
|
|
||||||
|
|
||||||
|
from moto.packages.boto.ec2.ec2object import EC2Object
|
||||||
|
|
||||||
|
|
||||||
|
class InstanceType(EC2Object):
|
||||||
|
"""
|
||||||
|
Represents an EC2 VM Type
|
||||||
|
|
||||||
|
:ivar name: The name of the vm type
|
||||||
|
:ivar cores: The number of cpu cores for this vm type
|
||||||
|
:ivar memory: The amount of memory in megabytes for this vm type
|
||||||
|
:ivar disk: The amount of disk space in gigabytes for this vm type
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, connection=None, name=None, cores=None, memory=None, disk=None):
|
||||||
|
super(InstanceType, self).__init__(connection)
|
||||||
|
self.connection = connection
|
||||||
|
self.name = name
|
||||||
|
self.cores = cores
|
||||||
|
self.memory = memory
|
||||||
|
self.disk = disk
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return "InstanceType:%s-%s,%s,%s" % (
|
||||||
|
self.name,
|
||||||
|
self.cores,
|
||||||
|
self.memory,
|
||||||
|
self.disk,
|
||||||
|
)
|
48
moto/packages/boto/ec2/launchspecification.py
Normal file
48
moto/packages/boto/ec2/launchspecification.py
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
# Copyright (c) 2006-2012 Mitch Garnaat http://garnaat.org/
|
||||||
|
# Copyright (c) 2012 Amazon.com, Inc. or its affiliates. All Rights Reserved
|
||||||
|
#
|
||||||
|
# Permission is hereby granted, free of charge, to any person obtaining a
|
||||||
|
# copy of this software and associated documentation files (the
|
||||||
|
# "Software"), to deal in the Software without restriction, including
|
||||||
|
# without limitation the rights to use, copy, modify, merge, publish, dis-
|
||||||
|
# tribute, sublicense, and/or sell copies of the Software, and to permit
|
||||||
|
# persons to whom the Software is furnished to do so, subject to the fol-
|
||||||
|
# lowing conditions:
|
||||||
|
#
|
||||||
|
# The above copyright notice and this permission notice shall be included
|
||||||
|
# in all copies or substantial portions of the Software.
|
||||||
|
#
|
||||||
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||||
|
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
|
||||||
|
# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||||
|
# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||||
|
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||||
|
# IN THE SOFTWARE.
|
||||||
|
|
||||||
|
"""
|
||||||
|
Represents a launch specification for Spot instances.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from moto.packages.boto.ec2.ec2object import EC2Object
|
||||||
|
|
||||||
|
|
||||||
|
class LaunchSpecification(EC2Object):
|
||||||
|
def __init__(self, connection=None):
|
||||||
|
super(LaunchSpecification, self).__init__(connection)
|
||||||
|
self.key_name = None
|
||||||
|
self.instance_type = None
|
||||||
|
self.image_id = None
|
||||||
|
self.groups = []
|
||||||
|
self.placement = None
|
||||||
|
self.kernel = None
|
||||||
|
self.ramdisk = None
|
||||||
|
self.monitored = False
|
||||||
|
self.subnet_id = None
|
||||||
|
self._in_monitoring_element = False
|
||||||
|
self.block_device_mapping = None
|
||||||
|
self.instance_profile = None
|
||||||
|
self.ebs_optimized = False
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return "LaunchSpecification(%s)" % self.image_id
|
85
moto/packages/boto/ec2/spotinstancerequest.py
Normal file
85
moto/packages/boto/ec2/spotinstancerequest.py
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
# Copyright (c) 2006-2010 Mitch Garnaat http://garnaat.org/
|
||||||
|
# Copyright (c) 2010, Eucalyptus Systems, Inc.
|
||||||
|
#
|
||||||
|
# Permission is hereby granted, free of charge, to any person obtaining a
|
||||||
|
# copy of this software and associated documentation files (the
|
||||||
|
# "Software"), to deal in the Software without restriction, including
|
||||||
|
# without limitation the rights to use, copy, modify, merge, publish, dis-
|
||||||
|
# tribute, sublicense, and/or sell copies of the Software, and to permit
|
||||||
|
# persons to whom the Software is furnished to do so, subject to the fol-
|
||||||
|
# lowing conditions:
|
||||||
|
#
|
||||||
|
# The above copyright notice and this permission notice shall be included
|
||||||
|
# in all copies or substantial portions of the Software.
|
||||||
|
#
|
||||||
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||||
|
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
|
||||||
|
# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||||
|
# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||||
|
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||||
|
# IN THE SOFTWARE.
|
||||||
|
|
||||||
|
"""
|
||||||
|
Represents an EC2 Spot Instance Request
|
||||||
|
"""
|
||||||
|
|
||||||
|
from moto.packages.boto.ec2.ec2object import TaggedEC2Object
|
||||||
|
|
||||||
|
|
||||||
|
class SpotInstanceRequest(TaggedEC2Object):
|
||||||
|
"""
|
||||||
|
|
||||||
|
:ivar id: The ID of the Spot Instance Request.
|
||||||
|
:ivar price: The maximum hourly price for any Spot Instance launched to
|
||||||
|
fulfill the request.
|
||||||
|
:ivar type: The Spot Instance request type.
|
||||||
|
:ivar state: The state of the Spot Instance request.
|
||||||
|
:ivar fault: The fault codes for the Spot Instance request, if any.
|
||||||
|
:ivar valid_from: The start date of the request. If this is a one-time
|
||||||
|
request, the request becomes active at this date and time and remains
|
||||||
|
active until all instances launch, the request expires, or the request is
|
||||||
|
canceled. If the request is persistent, the request becomes active at this
|
||||||
|
date and time and remains active until it expires or is canceled.
|
||||||
|
:ivar valid_until: The end date of the request. If this is a one-time
|
||||||
|
request, the request remains active until all instances launch, the request
|
||||||
|
is canceled, or this date is reached. If the request is persistent, it
|
||||||
|
remains active until it is canceled or this date is reached.
|
||||||
|
:ivar launch_group: The instance launch group. Launch groups are Spot
|
||||||
|
Instances that launch together and terminate together.
|
||||||
|
:ivar launched_availability_zone: foo
|
||||||
|
:ivar product_description: The Availability Zone in which the bid is
|
||||||
|
launched.
|
||||||
|
:ivar availability_zone_group: The Availability Zone group. If you specify
|
||||||
|
the same Availability Zone group for all Spot Instance requests, all Spot
|
||||||
|
Instances are launched in the same Availability Zone.
|
||||||
|
:ivar create_time: The time stamp when the Spot Instance request was
|
||||||
|
created.
|
||||||
|
:ivar launch_specification: Additional information for launching instances.
|
||||||
|
:ivar instance_id: The instance ID, if an instance has been launched to
|
||||||
|
fulfill the Spot Instance request.
|
||||||
|
:ivar status: The status code and status message describing the Spot
|
||||||
|
Instance request.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, connection=None):
|
||||||
|
super(SpotInstanceRequest, self).__init__(connection)
|
||||||
|
self.id = None
|
||||||
|
self.price = None
|
||||||
|
self.type = None
|
||||||
|
self.state = None
|
||||||
|
self.fault = None
|
||||||
|
self.valid_from = None
|
||||||
|
self.valid_until = None
|
||||||
|
self.launch_group = None
|
||||||
|
self.launched_availability_zone = None
|
||||||
|
self.product_description = None
|
||||||
|
self.availability_zone_group = None
|
||||||
|
self.create_time = None
|
||||||
|
self.launch_specification = None
|
||||||
|
self.instance_id = None
|
||||||
|
self.status = None
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return "SpotInstanceRequest:%s" % self.id
|
35
moto/packages/boto/ec2/tag.py
Normal file
35
moto/packages/boto/ec2/tag.py
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
# Copyright (c) 2010 Mitch Garnaat http://garnaat.org/
|
||||||
|
# Copyright (c) 2010, Eucalyptus Systems, Inc.
|
||||||
|
#
|
||||||
|
# Permission is hereby granted, free of charge, to any person obtaining a
|
||||||
|
# copy of this software and associated documentation files (the
|
||||||
|
# "Software"), to deal in the Software without restriction, including
|
||||||
|
# without limitation the rights to use, copy, modify, merge, publish, dis-
|
||||||
|
# tribute, sublicense, and/or sell copies of the Software, and to permit
|
||||||
|
# persons to whom the Software is furnished to do so, subject to the fol-
|
||||||
|
# lowing conditions:
|
||||||
|
#
|
||||||
|
# The above copyright notice and this permission notice shall be included
|
||||||
|
# in all copies or substantial portions of the Software.
|
||||||
|
#
|
||||||
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
||||||
|
# OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABIL-
|
||||||
|
# ITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT
|
||||||
|
# SHALL THE AUTHOR BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||||
|
# WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||||
|
# IN THE SOFTWARE.
|
||||||
|
|
||||||
|
|
||||||
|
class TagSet(dict):
|
||||||
|
"""
|
||||||
|
A TagSet is used to collect the tags associated with a particular
|
||||||
|
EC2 resource. Not all resources can be tagged but for those that
|
||||||
|
can, this dict object will be used to collect those values. See
|
||||||
|
:class:`boto.ec2.ec2object.TaggedEC2Object` for more details.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, connection=None):
|
||||||
|
self.connection = connection
|
||||||
|
self._current_key = None
|
||||||
|
self._current_value = None
|
@ -1,6 +1,6 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import boto.rds
|
from boto3 import Session
|
||||||
from jinja2 import Template
|
from jinja2 import Template
|
||||||
|
|
||||||
from moto.core import BaseBackend, CloudFormationModel
|
from moto.core import BaseBackend, CloudFormationModel
|
||||||
@ -335,6 +335,10 @@ class RDSBackend(BaseBackend):
|
|||||||
return rds2_backends[self.region]
|
return rds2_backends[self.region]
|
||||||
|
|
||||||
|
|
||||||
rds_backends = dict(
|
rds_backends = {}
|
||||||
(region.name, RDSBackend(region.name)) for region in boto.rds.regions()
|
for region in Session().get_available_regions("rds"):
|
||||||
)
|
rds_backends[region] = RDSBackend(region)
|
||||||
|
for region in Session().get_available_regions("rds", partition_name="aws-us-gov"):
|
||||||
|
rds_backends[region] = RDSBackend(region)
|
||||||
|
for region in Session().get_available_regions("rds", partition_name="aws-cn"):
|
||||||
|
rds_backends[region] = RDSBackend(region)
|
||||||
|
@ -9,7 +9,8 @@ from boto3 import Session
|
|||||||
from jinja2 import Template
|
from jinja2 import Template
|
||||||
from re import compile as re_compile
|
from re import compile as re_compile
|
||||||
from moto.compat import OrderedDict
|
from moto.compat import OrderedDict
|
||||||
from moto.core import BaseBackend, BaseModel, CloudFormationModel
|
from moto.core import BaseBackend, BaseModel, CloudFormationModel, ACCOUNT_ID
|
||||||
|
|
||||||
from moto.core.utils import iso_8601_datetime_with_milliseconds
|
from moto.core.utils import iso_8601_datetime_with_milliseconds
|
||||||
from moto.ec2.models import ec2_backends
|
from moto.ec2.models import ec2_backends
|
||||||
from .exceptions import (
|
from .exceptions import (
|
||||||
@ -157,6 +158,7 @@ class Database(CloudFormationModel):
|
|||||||
family=db_family,
|
family=db_family,
|
||||||
description=description,
|
description=description,
|
||||||
tags={},
|
tags={},
|
||||||
|
region=self.region,
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
else:
|
else:
|
||||||
@ -1172,7 +1174,7 @@ class RDS2Backend(BaseBackend):
|
|||||||
"InvalidParameterValue",
|
"InvalidParameterValue",
|
||||||
"The parameter DBParameterGroupName must be provided and must not be blank.",
|
"The parameter DBParameterGroupName must be provided and must not be blank.",
|
||||||
)
|
)
|
||||||
|
db_parameter_group_kwargs["region"] = self.region
|
||||||
db_parameter_group = DBParameterGroup(**db_parameter_group_kwargs)
|
db_parameter_group = DBParameterGroup(**db_parameter_group_kwargs)
|
||||||
self.db_parameter_groups[db_parameter_group_id] = db_parameter_group
|
self.db_parameter_groups[db_parameter_group_id] = db_parameter_group
|
||||||
return db_parameter_group
|
return db_parameter_group
|
||||||
@ -1471,13 +1473,18 @@ class OptionGroupOptionSetting(object):
|
|||||||
return template.render(option_group_option_setting=self)
|
return template.render(option_group_option_setting=self)
|
||||||
|
|
||||||
|
|
||||||
|
def make_rds_arn(region, name):
|
||||||
|
return "arn:aws:rds:{0}:{1}:pg:{2}".format(region, ACCOUNT_ID, name)
|
||||||
|
|
||||||
|
|
||||||
class DBParameterGroup(CloudFormationModel):
|
class DBParameterGroup(CloudFormationModel):
|
||||||
def __init__(self, name, description, family, tags):
|
def __init__(self, name, description, family, tags, region):
|
||||||
self.name = name
|
self.name = name
|
||||||
self.description = description
|
self.description = description
|
||||||
self.family = family
|
self.family = family
|
||||||
self.tags = tags
|
self.tags = tags
|
||||||
self.parameters = defaultdict(dict)
|
self.parameters = defaultdict(dict)
|
||||||
|
self.arn = make_rds_arn(region, name)
|
||||||
|
|
||||||
def to_xml(self):
|
def to_xml(self):
|
||||||
template = Template(
|
template = Template(
|
||||||
@ -1485,6 +1492,7 @@ class DBParameterGroup(CloudFormationModel):
|
|||||||
<DBParameterGroupName>{{ param_group.name }}</DBParameterGroupName>
|
<DBParameterGroupName>{{ param_group.name }}</DBParameterGroupName>
|
||||||
<DBParameterGroupFamily>{{ param_group.family }}</DBParameterGroupFamily>
|
<DBParameterGroupFamily>{{ param_group.family }}</DBParameterGroupFamily>
|
||||||
<Description>{{ param_group.description }}</Description>
|
<Description>{{ param_group.description }}</Description>
|
||||||
|
<DBParameterGroupArn>{{ param_group.arn }}</DBParameterGroupArn>
|
||||||
</DBParameterGroup>"""
|
</DBParameterGroup>"""
|
||||||
)
|
)
|
||||||
return template.render(param_group=self)
|
return template.render(param_group=self)
|
||||||
|
@ -143,3 +143,17 @@ class ClusterAlreadyExistsFaultError(RedshiftClientError):
|
|||||||
super(ClusterAlreadyExistsFaultError, self).__init__(
|
super(ClusterAlreadyExistsFaultError, self).__init__(
|
||||||
"ClusterAlreadyExists", "Cluster already exists"
|
"ClusterAlreadyExists", "Cluster already exists"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class InvalidParameterCombinationError(RedshiftClientError):
|
||||||
|
def __init__(self, message):
|
||||||
|
super(InvalidParameterCombinationError, self).__init__(
|
||||||
|
"InvalidParameterCombination", message
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class UnknownSnapshotCopyRegionFaultError(RedshiftClientError):
|
||||||
|
def __init__(self, message):
|
||||||
|
super(UnknownSnapshotCopyRegionFaultError, self).__init__(
|
||||||
|
"UnknownSnapshotCopyRegionFault", message
|
||||||
|
)
|
||||||
|
@ -4,7 +4,7 @@ import copy
|
|||||||
import datetime
|
import datetime
|
||||||
|
|
||||||
from boto3 import Session
|
from boto3 import Session
|
||||||
from botocore.exceptions import ClientError
|
|
||||||
from moto.compat import OrderedDict
|
from moto.compat import OrderedDict
|
||||||
from moto.core import BaseBackend, BaseModel, CloudFormationModel
|
from moto.core import BaseBackend, BaseModel, CloudFormationModel
|
||||||
from moto.core.utils import iso_8601_datetime_with_milliseconds
|
from moto.core.utils import iso_8601_datetime_with_milliseconds
|
||||||
@ -17,6 +17,7 @@ from .exceptions import (
|
|||||||
ClusterSnapshotAlreadyExistsError,
|
ClusterSnapshotAlreadyExistsError,
|
||||||
ClusterSnapshotNotFoundError,
|
ClusterSnapshotNotFoundError,
|
||||||
ClusterSubnetGroupNotFoundError,
|
ClusterSubnetGroupNotFoundError,
|
||||||
|
InvalidParameterCombinationError,
|
||||||
InvalidParameterValueError,
|
InvalidParameterValueError,
|
||||||
InvalidSubnetError,
|
InvalidSubnetError,
|
||||||
ResourceNotFoundFaultError,
|
ResourceNotFoundFaultError,
|
||||||
@ -25,6 +26,7 @@ from .exceptions import (
|
|||||||
SnapshotCopyDisabledFaultError,
|
SnapshotCopyDisabledFaultError,
|
||||||
SnapshotCopyGrantAlreadyExistsFaultError,
|
SnapshotCopyGrantAlreadyExistsFaultError,
|
||||||
SnapshotCopyGrantNotFoundFaultError,
|
SnapshotCopyGrantNotFoundFaultError,
|
||||||
|
UnknownSnapshotCopyRegionFaultError,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@ -576,6 +578,10 @@ class RedshiftBackend(BaseBackend):
|
|||||||
raise InvalidParameterValueError(
|
raise InvalidParameterValueError(
|
||||||
"SnapshotCopyGrantName is required for Snapshot Copy on KMS encrypted clusters."
|
"SnapshotCopyGrantName is required for Snapshot Copy on KMS encrypted clusters."
|
||||||
)
|
)
|
||||||
|
if kwargs["destination_region"] == self.region:
|
||||||
|
raise UnknownSnapshotCopyRegionFaultError(
|
||||||
|
"Invalid region {}".format(self.region)
|
||||||
|
)
|
||||||
status = {
|
status = {
|
||||||
"DestinationRegion": kwargs["destination_region"],
|
"DestinationRegion": kwargs["destination_region"],
|
||||||
"RetentionPeriod": kwargs["retention_period"],
|
"RetentionPeriod": kwargs["retention_period"],
|
||||||
@ -655,10 +661,8 @@ class RedshiftBackend(BaseBackend):
|
|||||||
cluster_skip_final_snapshot is False
|
cluster_skip_final_snapshot is False
|
||||||
and cluster_snapshot_identifer is None
|
and cluster_snapshot_identifer is None
|
||||||
):
|
):
|
||||||
raise ClientError(
|
raise InvalidParameterCombinationError(
|
||||||
"InvalidParameterValue",
|
"FinalClusterSnapshotIdentifier is required unless SkipFinalClusterSnapshot is specified."
|
||||||
"FinalSnapshotIdentifier is required for Snapshot copy "
|
|
||||||
"when SkipFinalSnapshot is False",
|
|
||||||
)
|
)
|
||||||
elif (
|
elif (
|
||||||
cluster_skip_final_snapshot is False
|
cluster_skip_final_snapshot is False
|
||||||
@ -777,7 +781,6 @@ class RedshiftBackend(BaseBackend):
|
|||||||
cluster_snapshots.append(snapshot)
|
cluster_snapshots.append(snapshot)
|
||||||
if cluster_snapshots:
|
if cluster_snapshots:
|
||||||
return cluster_snapshots
|
return cluster_snapshots
|
||||||
raise ClusterNotFoundError(cluster_identifier)
|
|
||||||
|
|
||||||
if snapshot_identifier:
|
if snapshot_identifier:
|
||||||
if snapshot_identifier in self.snapshots:
|
if snapshot_identifier in self.snapshots:
|
||||||
|
@ -93,6 +93,11 @@ class DomainDispatcherApplication(object):
|
|||||||
# S3 is the last resort when the target is also unknown
|
# S3 is the last resort when the target is also unknown
|
||||||
service, region = DEFAULT_SERVICE_REGION
|
service, region = DEFAULT_SERVICE_REGION
|
||||||
|
|
||||||
|
if service == "EventBridge":
|
||||||
|
# Go SDK uses 'EventBridge' in the SigV4 request instead of 'events'
|
||||||
|
# see https://github.com/spulec/moto/issues/3494
|
||||||
|
service = "events"
|
||||||
|
|
||||||
if service == "dynamodb":
|
if service == "dynamodb":
|
||||||
if environ["HTTP_X_AMZ_TARGET"].startswith("DynamoDBStreams"):
|
if environ["HTTP_X_AMZ_TARGET"].startswith("DynamoDBStreams"):
|
||||||
host = "dynamodbstreams"
|
host = "dynamodbstreams"
|
||||||
|
1
setup.py
1
setup.py
@ -32,7 +32,6 @@ def get_version():
|
|||||||
|
|
||||||
|
|
||||||
install_requires = [
|
install_requires = [
|
||||||
"boto>=2.36.0",
|
|
||||||
"boto3>=1.9.201",
|
"boto3>=1.9.201",
|
||||||
"botocore>=1.12.201",
|
"botocore>=1.12.201",
|
||||||
"cryptography>=2.3.0",
|
"cryptography>=2.3.0",
|
||||||
|
@ -15,7 +15,7 @@ from moto.cloudformation.parsing import (
|
|||||||
from moto.sqs.models import Queue
|
from moto.sqs.models import Queue
|
||||||
from moto.s3.models import FakeBucket
|
from moto.s3.models import FakeBucket
|
||||||
from moto.cloudformation.utils import yaml_tag_constructor
|
from moto.cloudformation.utils import yaml_tag_constructor
|
||||||
from boto.cloudformation.stack import Output
|
from moto.packages.boto.cloudformation.stack import Output
|
||||||
|
|
||||||
|
|
||||||
dummy_template = {
|
dummy_template = {
|
||||||
|
@ -1840,6 +1840,31 @@ def test_admin_set_user_password():
|
|||||||
result["UserStatus"].should.equal("CONFIRMED")
|
result["UserStatus"].should.equal("CONFIRMED")
|
||||||
|
|
||||||
|
|
||||||
|
@mock_cognitoidp
|
||||||
|
def test_change_password_with_invalid_token_raises_error():
|
||||||
|
client = boto3.client("cognito-idp", "us-west-2")
|
||||||
|
with pytest.raises(ClientError) as ex:
|
||||||
|
client.change_password(
|
||||||
|
AccessToken=str(uuid.uuid4()),
|
||||||
|
PreviousPassword="previous_password",
|
||||||
|
ProposedPassword="newer_password",
|
||||||
|
)
|
||||||
|
ex.value.response["Error"]["Code"].should.equal("NotAuthorizedException")
|
||||||
|
|
||||||
|
|
||||||
|
@mock_cognitoidp
|
||||||
|
def test_confirm_forgot_password_with_non_existent_client_id_raises_error():
|
||||||
|
client = boto3.client("cognito-idp", "us-west-2")
|
||||||
|
with pytest.raises(ClientError) as ex:
|
||||||
|
client.confirm_forgot_password(
|
||||||
|
ClientId="non-existent-client-id",
|
||||||
|
Username="not-existent-username",
|
||||||
|
ConfirmationCode=str(uuid.uuid4()),
|
||||||
|
Password=str(uuid.uuid4()),
|
||||||
|
)
|
||||||
|
ex.value.response["Error"]["Code"].should.equal("ResourceNotFoundException")
|
||||||
|
|
||||||
|
|
||||||
# Test will retrieve public key from cognito.amazonaws.com/.well-known/jwks.json,
|
# Test will retrieve public key from cognito.amazonaws.com/.well-known/jwks.json,
|
||||||
# which isnt mocked in ServerMode
|
# which isnt mocked in ServerMode
|
||||||
if not settings.TEST_SERVER_MODE:
|
if not settings.TEST_SERVER_MODE:
|
||||||
|
@ -5523,6 +5523,61 @@ def test_gsi_projection_type_keys_only():
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@mock_dynamodb2
|
||||||
|
def test_gsi_projection_type_include():
|
||||||
|
table_schema = {
|
||||||
|
"KeySchema": [{"AttributeName": "partitionKey", "KeyType": "HASH"}],
|
||||||
|
"GlobalSecondaryIndexes": [
|
||||||
|
{
|
||||||
|
"IndexName": "GSI-INC",
|
||||||
|
"KeySchema": [
|
||||||
|
{"AttributeName": "gsiK1PartitionKey", "KeyType": "HASH"},
|
||||||
|
{"AttributeName": "gsiK1SortKey", "KeyType": "RANGE"},
|
||||||
|
],
|
||||||
|
"Projection": {
|
||||||
|
"ProjectionType": "INCLUDE",
|
||||||
|
"NonKeyAttributes": ["projectedAttribute"],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"AttributeDefinitions": [
|
||||||
|
{"AttributeName": "partitionKey", "AttributeType": "S"},
|
||||||
|
{"AttributeName": "gsiK1PartitionKey", "AttributeType": "S"},
|
||||||
|
{"AttributeName": "gsiK1SortKey", "AttributeType": "S"},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
|
||||||
|
item = {
|
||||||
|
"partitionKey": "pk-1",
|
||||||
|
"gsiK1PartitionKey": "gsi-pk",
|
||||||
|
"gsiK1SortKey": "gsi-sk",
|
||||||
|
"projectedAttribute": "lore ipsum",
|
||||||
|
"nonProjectedAttribute": "dolor sit amet",
|
||||||
|
}
|
||||||
|
|
||||||
|
dynamodb = boto3.resource("dynamodb", region_name="us-east-1")
|
||||||
|
dynamodb.create_table(
|
||||||
|
TableName="test-table", BillingMode="PAY_PER_REQUEST", **table_schema
|
||||||
|
)
|
||||||
|
table = dynamodb.Table("test-table")
|
||||||
|
table.put_item(Item=item)
|
||||||
|
|
||||||
|
items = table.query(
|
||||||
|
KeyConditionExpression=Key("gsiK1PartitionKey").eq("gsi-pk"),
|
||||||
|
IndexName="GSI-INC",
|
||||||
|
)["Items"]
|
||||||
|
items.should.have.length_of(1)
|
||||||
|
# Item should only include keys and additionally projected attributes only
|
||||||
|
items[0].should.equal(
|
||||||
|
{
|
||||||
|
"gsiK1PartitionKey": "gsi-pk",
|
||||||
|
"gsiK1SortKey": "gsi-sk",
|
||||||
|
"partitionKey": "pk-1",
|
||||||
|
"projectedAttribute": "lore ipsum",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@mock_dynamodb2
|
@mock_dynamodb2
|
||||||
def test_lsi_projection_type_keys_only():
|
def test_lsi_projection_type_keys_only():
|
||||||
table_schema = {
|
table_schema = {
|
||||||
|
@ -2,6 +2,9 @@ from moto import mock_cloudformation_deprecated, mock_ec2_deprecated
|
|||||||
from moto import mock_cloudformation, mock_ec2
|
from moto import mock_cloudformation, mock_ec2
|
||||||
from tests.test_cloudformation.fixtures import vpc_eni
|
from tests.test_cloudformation.fixtures import vpc_eni
|
||||||
import boto
|
import boto
|
||||||
|
import boto.ec2
|
||||||
|
import boto.cloudformation
|
||||||
|
import boto.vpc
|
||||||
import boto3
|
import boto3
|
||||||
import json
|
import json
|
||||||
import sure # noqa
|
import sure # noqa
|
||||||
|
@ -636,7 +636,7 @@ def test_run_job_flow_with_custom_ami():
|
|||||||
|
|
||||||
args = deepcopy(run_job_flow_args)
|
args = deepcopy(run_job_flow_args)
|
||||||
args["CustomAmiId"] = "MyEmrCustomAmi"
|
args["CustomAmiId"] = "MyEmrCustomAmi"
|
||||||
args["ReleaseLabel"] = "emr-5.7.0"
|
args["ReleaseLabel"] = "emr-5.31.0"
|
||||||
cluster_id = client.run_job_flow(**args)["JobFlowId"]
|
cluster_id = client.run_job_flow(**args)["JobFlowId"]
|
||||||
resp = client.describe_cluster(ClusterId=cluster_id)
|
resp = client.describe_cluster(ClusterId=cluster_id)
|
||||||
resp["Cluster"]["CustomAmiId"].should.equal("MyEmrCustomAmi")
|
resp["Cluster"]["CustomAmiId"].should.equal("MyEmrCustomAmi")
|
||||||
|
49
tests/test_emr/test_utils.py
Normal file
49
tests/test_emr/test_utils.py
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
import pytest
|
||||||
|
|
||||||
|
from moto.emr.utils import ReleaseLabel
|
||||||
|
|
||||||
|
|
||||||
|
def test_invalid_release_labels_raise_exception():
|
||||||
|
invalid_releases = [
|
||||||
|
"",
|
||||||
|
"0",
|
||||||
|
"1.0",
|
||||||
|
"emr-2.0",
|
||||||
|
]
|
||||||
|
for invalid_release in invalid_releases:
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
ReleaseLabel(invalid_release)
|
||||||
|
|
||||||
|
|
||||||
|
def test_release_label_comparisons():
|
||||||
|
assert str(ReleaseLabel("emr-5.1.2")) == "emr-5.1.2"
|
||||||
|
|
||||||
|
assert ReleaseLabel("emr-5.0.0") != ReleaseLabel("emr-5.0.1")
|
||||||
|
assert ReleaseLabel("emr-5.0.0") == ReleaseLabel("emr-5.0.0")
|
||||||
|
|
||||||
|
assert ReleaseLabel("emr-5.31.0") > ReleaseLabel("emr-5.7.0")
|
||||||
|
assert ReleaseLabel("emr-6.0.0") > ReleaseLabel("emr-5.7.0")
|
||||||
|
|
||||||
|
assert ReleaseLabel("emr-5.7.0") < ReleaseLabel("emr-5.10.0")
|
||||||
|
assert ReleaseLabel("emr-5.10.0") < ReleaseLabel("emr-5.10.1")
|
||||||
|
|
||||||
|
assert ReleaseLabel("emr-5.60.0") >= ReleaseLabel("emr-5.7.0")
|
||||||
|
assert ReleaseLabel("emr-6.0.0") >= ReleaseLabel("emr-6.0.0")
|
||||||
|
|
||||||
|
assert ReleaseLabel("emr-5.7.0") <= ReleaseLabel("emr-5.17.0")
|
||||||
|
assert ReleaseLabel("emr-5.7.0") <= ReleaseLabel("emr-5.7.0")
|
||||||
|
|
||||||
|
releases_unsorted = [
|
||||||
|
ReleaseLabel("emr-5.60.2"),
|
||||||
|
ReleaseLabel("emr-4.0.1"),
|
||||||
|
ReleaseLabel("emr-4.0.0"),
|
||||||
|
ReleaseLabel("emr-5.7.3"),
|
||||||
|
]
|
||||||
|
releases_sorted = [str(label) for label in sorted(releases_unsorted)]
|
||||||
|
expected = [
|
||||||
|
"emr-4.0.0",
|
||||||
|
"emr-4.0.1",
|
||||||
|
"emr-5.7.3",
|
||||||
|
"emr-5.60.2",
|
||||||
|
]
|
||||||
|
assert releases_sorted == expected
|
@ -3,6 +3,7 @@ import json
|
|||||||
import boto3
|
import boto3
|
||||||
from botocore.exceptions import ClientError
|
from botocore.exceptions import ClientError
|
||||||
import pytest
|
import pytest
|
||||||
|
import sure # noqa
|
||||||
|
|
||||||
from moto import mock_iam
|
from moto import mock_iam
|
||||||
|
|
||||||
@ -1611,31 +1612,25 @@ valid_policy_documents = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
def test_create_policy_with_invalid_policy_documents():
|
@pytest.mark.parametrize("invalid_policy_document", invalid_policy_document_test_cases)
|
||||||
for test_case in invalid_policy_document_test_cases:
|
|
||||||
yield check_create_policy_with_invalid_policy_document, test_case
|
|
||||||
|
|
||||||
|
|
||||||
def test_create_policy_with_valid_policy_documents():
|
|
||||||
for valid_policy_document in valid_policy_documents:
|
|
||||||
yield check_create_policy_with_valid_policy_document, valid_policy_document
|
|
||||||
|
|
||||||
|
|
||||||
@mock_iam
|
@mock_iam
|
||||||
def check_create_policy_with_invalid_policy_document(test_case):
|
def test_create_policy_with_invalid_policy_document(invalid_policy_document):
|
||||||
conn = boto3.client("iam", region_name="us-east-1")
|
conn = boto3.client("iam", region_name="us-east-1")
|
||||||
with pytest.raises(ClientError) as ex:
|
with pytest.raises(ClientError) as ex:
|
||||||
conn.create_policy(
|
conn.create_policy(
|
||||||
PolicyName="TestCreatePolicy",
|
PolicyName="TestCreatePolicy",
|
||||||
PolicyDocument=json.dumps(test_case["document"]),
|
PolicyDocument=json.dumps(invalid_policy_document["document"]),
|
||||||
)
|
)
|
||||||
ex.value.response["Error"]["Code"].should.equal("MalformedPolicyDocument")
|
ex.value.response["Error"]["Code"].should.equal("MalformedPolicyDocument")
|
||||||
ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
|
ex.value.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
|
||||||
ex.value.response["Error"]["Message"].should.equal(test_case["error_message"])
|
ex.value.response["Error"]["Message"].should.equal(
|
||||||
|
invalid_policy_document["error_message"]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize("valid_policy_document", valid_policy_documents)
|
||||||
@mock_iam
|
@mock_iam
|
||||||
def check_create_policy_with_valid_policy_document(valid_policy_document):
|
def test_create_policy_with_valid_policy_document(valid_policy_document):
|
||||||
conn = boto3.client("iam", region_name="us-east-1")
|
conn = boto3.client("iam", region_name="us-east-1")
|
||||||
conn.create_policy(
|
conn.create_policy(
|
||||||
PolicyName="TestCreatePolicy", PolicyDocument=json.dumps(valid_policy_document)
|
PolicyName="TestCreatePolicy", PolicyDocument=json.dumps(valid_policy_document)
|
||||||
|
@ -4,6 +4,7 @@ from botocore.exceptions import ClientError, ParamValidationError
|
|||||||
import boto3
|
import boto3
|
||||||
import sure # noqa
|
import sure # noqa
|
||||||
from moto import mock_ec2, mock_kms, mock_rds2
|
from moto import mock_ec2, mock_kms, mock_rds2
|
||||||
|
from moto.core import ACCOUNT_ID
|
||||||
|
|
||||||
|
|
||||||
@mock_rds2
|
@mock_rds2
|
||||||
@ -1504,7 +1505,9 @@ def test_create_database_with_encrypted_storage():
|
|||||||
|
|
||||||
@mock_rds2
|
@mock_rds2
|
||||||
def test_create_db_parameter_group():
|
def test_create_db_parameter_group():
|
||||||
conn = boto3.client("rds", region_name="us-west-2")
|
region = "us-west-2"
|
||||||
|
pg_name = "test"
|
||||||
|
conn = boto3.client("rds", region_name=region)
|
||||||
db_parameter_group = conn.create_db_parameter_group(
|
db_parameter_group = conn.create_db_parameter_group(
|
||||||
DBParameterGroupName="test",
|
DBParameterGroupName="test",
|
||||||
DBParameterGroupFamily="mysql5.6",
|
DBParameterGroupFamily="mysql5.6",
|
||||||
@ -1518,6 +1521,9 @@ def test_create_db_parameter_group():
|
|||||||
db_parameter_group["DBParameterGroup"]["Description"].should.equal(
|
db_parameter_group["DBParameterGroup"]["Description"].should.equal(
|
||||||
"test parameter group"
|
"test parameter group"
|
||||||
)
|
)
|
||||||
|
db_parameter_group["DBParameterGroup"]["DBParameterGroupArn"].should.equal(
|
||||||
|
"arn:aws:rds:{0}:{1}:pg:{2}".format(region, ACCOUNT_ID, pg_name)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@mock_rds2
|
@mock_rds2
|
||||||
@ -1629,9 +1635,11 @@ def test_create_db_parameter_group_duplicate():
|
|||||||
|
|
||||||
@mock_rds2
|
@mock_rds2
|
||||||
def test_describe_db_parameter_group():
|
def test_describe_db_parameter_group():
|
||||||
conn = boto3.client("rds", region_name="us-west-2")
|
region = "us-west-2"
|
||||||
|
pg_name = "test"
|
||||||
|
conn = boto3.client("rds", region_name=region)
|
||||||
conn.create_db_parameter_group(
|
conn.create_db_parameter_group(
|
||||||
DBParameterGroupName="test",
|
DBParameterGroupName=pg_name,
|
||||||
DBParameterGroupFamily="mysql5.6",
|
DBParameterGroupFamily="mysql5.6",
|
||||||
Description="test parameter group",
|
Description="test parameter group",
|
||||||
)
|
)
|
||||||
@ -1639,6 +1647,9 @@ def test_describe_db_parameter_group():
|
|||||||
db_parameter_groups["DBParameterGroups"][0]["DBParameterGroupName"].should.equal(
|
db_parameter_groups["DBParameterGroups"][0]["DBParameterGroupName"].should.equal(
|
||||||
"test"
|
"test"
|
||||||
)
|
)
|
||||||
|
db_parameter_groups["DBParameterGroups"][0]["DBParameterGroupArn"].should.equal(
|
||||||
|
"arn:aws:rds:{0}:{1}:pg:{2}".format(region, ACCOUNT_ID, pg_name)
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@mock_rds2
|
@mock_rds2
|
||||||
|
@ -43,7 +43,7 @@ def test_create_cluster_boto3():
|
|||||||
|
|
||||||
|
|
||||||
@mock_redshift
|
@mock_redshift
|
||||||
def test_create_cluster_boto3():
|
def test_create_cluster_with_enhanced_vpc_routing_enabled():
|
||||||
client = boto3.client("redshift", region_name="us-east-1")
|
client = boto3.client("redshift", region_name="us-east-1")
|
||||||
response = client.create_cluster(
|
response = client.create_cluster(
|
||||||
DBName="test",
|
DBName="test",
|
||||||
@ -76,7 +76,7 @@ def test_create_snapshot_copy_grant():
|
|||||||
|
|
||||||
client.describe_snapshot_copy_grants.when.called_with(
|
client.describe_snapshot_copy_grants.when.called_with(
|
||||||
SnapshotCopyGrantName="test-us-east-1"
|
SnapshotCopyGrantName="test-us-east-1"
|
||||||
).should.throw(Exception)
|
).should.throw(ClientError)
|
||||||
|
|
||||||
|
|
||||||
@mock_redshift
|
@mock_redshift
|
||||||
@ -424,7 +424,7 @@ def test_delete_cluster():
|
|||||||
)
|
)
|
||||||
|
|
||||||
conn.delete_cluster.when.called_with(cluster_identifier, False).should.throw(
|
conn.delete_cluster.when.called_with(cluster_identifier, False).should.throw(
|
||||||
AttributeError
|
boto.exception.JSONResponseError
|
||||||
)
|
)
|
||||||
|
|
||||||
clusters = conn.describe_clusters()["DescribeClustersResponse"][
|
clusters = conn.describe_clusters()["DescribeClustersResponse"][
|
||||||
@ -826,12 +826,11 @@ def test_describe_cluster_snapshots():
|
|||||||
@mock_redshift
|
@mock_redshift
|
||||||
def test_describe_cluster_snapshots_not_found_error():
|
def test_describe_cluster_snapshots_not_found_error():
|
||||||
client = boto3.client("redshift", region_name="us-east-1")
|
client = boto3.client("redshift", region_name="us-east-1")
|
||||||
cluster_identifier = "my_cluster"
|
cluster_identifier = "non-existent-cluster-id"
|
||||||
snapshot_identifier = "my_snapshot"
|
snapshot_identifier = "non-existent-snapshot-id"
|
||||||
|
|
||||||
client.describe_cluster_snapshots.when.called_with(
|
resp = client.describe_cluster_snapshots(ClusterIdentifier=cluster_identifier)
|
||||||
ClusterIdentifier=cluster_identifier
|
resp["Snapshots"].should.have.length_of(0)
|
||||||
).should.throw(ClientError, "Cluster {} not found.".format(cluster_identifier))
|
|
||||||
|
|
||||||
client.describe_cluster_snapshots.when.called_with(
|
client.describe_cluster_snapshots.when.called_with(
|
||||||
SnapshotIdentifier=snapshot_identifier
|
SnapshotIdentifier=snapshot_identifier
|
||||||
@ -867,8 +866,8 @@ def test_delete_cluster_snapshot():
|
|||||||
|
|
||||||
# Delete invalid id
|
# Delete invalid id
|
||||||
client.delete_cluster_snapshot.when.called_with(
|
client.delete_cluster_snapshot.when.called_with(
|
||||||
SnapshotIdentifier="not-a-snapshot"
|
SnapshotIdentifier="non-existent"
|
||||||
).should.throw(ClientError)
|
).should.throw(ClientError, "Snapshot non-existent not found.")
|
||||||
|
|
||||||
|
|
||||||
@mock_redshift
|
@mock_redshift
|
||||||
@ -892,7 +891,7 @@ def test_cluster_snapshot_already_exists():
|
|||||||
|
|
||||||
client.create_cluster_snapshot.when.called_with(
|
client.create_cluster_snapshot.when.called_with(
|
||||||
SnapshotIdentifier=snapshot_identifier, ClusterIdentifier=cluster_identifier
|
SnapshotIdentifier=snapshot_identifier, ClusterIdentifier=cluster_identifier
|
||||||
).should.throw(ClientError)
|
).should.throw(ClientError, "{} already exists".format(snapshot_identifier))
|
||||||
|
|
||||||
|
|
||||||
@mock_redshift
|
@mock_redshift
|
||||||
@ -1269,6 +1268,15 @@ def test_enable_snapshot_copy():
|
|||||||
ex.value.response["Error"]["Message"].should.contain(
|
ex.value.response["Error"]["Message"].should.contain(
|
||||||
"SnapshotCopyGrantName is required for Snapshot Copy on KMS encrypted clusters."
|
"SnapshotCopyGrantName is required for Snapshot Copy on KMS encrypted clusters."
|
||||||
)
|
)
|
||||||
|
with pytest.raises(ClientError) as ex:
|
||||||
|
client.enable_snapshot_copy(
|
||||||
|
ClusterIdentifier="test",
|
||||||
|
DestinationRegion="us-east-1",
|
||||||
|
RetentionPeriod=3,
|
||||||
|
SnapshotCopyGrantName="invalid-us-east-1-to-us-east-1",
|
||||||
|
)
|
||||||
|
ex.value.response["Error"]["Code"].should.equal("UnknownSnapshotCopyRegionFault")
|
||||||
|
ex.value.response["Error"]["Message"].should.contain("Invalid region us-east-1")
|
||||||
client.enable_snapshot_copy(
|
client.enable_snapshot_copy(
|
||||||
ClusterIdentifier="test",
|
ClusterIdentifier="test",
|
||||||
DestinationRegion="us-west-2",
|
DestinationRegion="us-west-2",
|
||||||
@ -1364,3 +1372,74 @@ def test_create_duplicate_cluster_fails():
|
|||||||
client.create_cluster.when.called_with(**kwargs).should.throw(
|
client.create_cluster.when.called_with(**kwargs).should.throw(
|
||||||
ClientError, "ClusterAlreadyExists"
|
ClientError, "ClusterAlreadyExists"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@mock_redshift
|
||||||
|
def test_delete_cluster_with_final_snapshot():
|
||||||
|
client = boto3.client("redshift", region_name="us-east-1")
|
||||||
|
|
||||||
|
with pytest.raises(ClientError) as ex:
|
||||||
|
client.delete_cluster(ClusterIdentifier="non-existent")
|
||||||
|
ex.value.response["Error"]["Code"].should.equal("ClusterNotFound")
|
||||||
|
ex.value.response["Error"]["Message"].should.match(r"Cluster .+ not found.")
|
||||||
|
|
||||||
|
cluster_identifier = "my_cluster"
|
||||||
|
client.create_cluster(
|
||||||
|
ClusterIdentifier=cluster_identifier,
|
||||||
|
ClusterType="single-node",
|
||||||
|
DBName="test",
|
||||||
|
MasterUsername="user",
|
||||||
|
MasterUserPassword="password",
|
||||||
|
NodeType="ds2.xlarge",
|
||||||
|
)
|
||||||
|
|
||||||
|
with pytest.raises(ClientError) as ex:
|
||||||
|
client.delete_cluster(
|
||||||
|
ClusterIdentifier=cluster_identifier, SkipFinalClusterSnapshot=False
|
||||||
|
)
|
||||||
|
ex.value.response["Error"]["Code"].should.equal("InvalidParameterCombination")
|
||||||
|
ex.value.response["Error"]["Message"].should.contain(
|
||||||
|
"FinalClusterSnapshotIdentifier is required unless SkipFinalClusterSnapshot is specified."
|
||||||
|
)
|
||||||
|
|
||||||
|
snapshot_identifier = "my_snapshot"
|
||||||
|
client.delete_cluster(
|
||||||
|
ClusterIdentifier=cluster_identifier,
|
||||||
|
SkipFinalClusterSnapshot=False,
|
||||||
|
FinalClusterSnapshotIdentifier=snapshot_identifier,
|
||||||
|
)
|
||||||
|
|
||||||
|
resp = client.describe_cluster_snapshots(ClusterIdentifier=cluster_identifier)
|
||||||
|
resp["Snapshots"].should.have.length_of(1)
|
||||||
|
resp["Snapshots"][0]["SnapshotIdentifier"].should.equal(snapshot_identifier)
|
||||||
|
resp["Snapshots"][0]["SnapshotType"].should.equal("manual")
|
||||||
|
|
||||||
|
with pytest.raises(ClientError) as ex:
|
||||||
|
client.describe_clusters(ClusterIdentifier=cluster_identifier)
|
||||||
|
ex.value.response["Error"]["Code"].should.equal("ClusterNotFound")
|
||||||
|
ex.value.response["Error"]["Message"].should.match(r"Cluster .+ not found.")
|
||||||
|
|
||||||
|
|
||||||
|
@mock_redshift
|
||||||
|
def test_delete_cluster_without_final_snapshot():
|
||||||
|
client = boto3.client("redshift", region_name="us-east-1")
|
||||||
|
cluster_identifier = "my_cluster"
|
||||||
|
client.create_cluster(
|
||||||
|
ClusterIdentifier=cluster_identifier,
|
||||||
|
ClusterType="single-node",
|
||||||
|
DBName="test",
|
||||||
|
MasterUsername="user",
|
||||||
|
MasterUserPassword="password",
|
||||||
|
NodeType="ds2.xlarge",
|
||||||
|
)
|
||||||
|
client.delete_cluster(
|
||||||
|
ClusterIdentifier=cluster_identifier, SkipFinalClusterSnapshot=True
|
||||||
|
)
|
||||||
|
|
||||||
|
resp = client.describe_cluster_snapshots(ClusterIdentifier=cluster_identifier)
|
||||||
|
resp["Snapshots"].should.have.length_of(0)
|
||||||
|
|
||||||
|
with pytest.raises(ClientError) as ex:
|
||||||
|
client.describe_clusters(ClusterIdentifier=cluster_identifier)
|
||||||
|
ex.value.response["Error"]["Code"].should.equal("ClusterNotFound")
|
||||||
|
ex.value.response["Error"]["Message"].should.match(r"Cluster .+ not found.")
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
import json
|
|
||||||
import sure # noqa
|
import sure # noqa
|
||||||
|
|
||||||
import moto.server as server
|
import moto.server as server
|
||||||
@ -20,3 +19,14 @@ def test_describe_clusters():
|
|||||||
|
|
||||||
result = res.data.decode("utf-8")
|
result = res.data.decode("utf-8")
|
||||||
result.should.contain("<Clusters></Clusters>")
|
result.should.contain("<Clusters></Clusters>")
|
||||||
|
|
||||||
|
|
||||||
|
@mock_redshift
|
||||||
|
def test_describe_clusters_with_json_content_type():
|
||||||
|
backend = server.create_backend_app("redshift")
|
||||||
|
test_client = backend.test_client()
|
||||||
|
|
||||||
|
res = test_client.get("/?Action=DescribeClusters&ContentType=JSON")
|
||||||
|
|
||||||
|
result = res.data.decode("utf-8")
|
||||||
|
result.should.contain('{"Clusters": []}')
|
||||||
|
@ -1,8 +1,4 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
set -e
|
set -e
|
||||||
# TravisCI on bionic dist uses old version of Docker Engine
|
|
||||||
# which is incompatibile with newer docker-py
|
|
||||||
# See https://github.com/docker/docker-py/issues/2639
|
|
||||||
pip install "docker>=2.5.1,<=4.2.2"
|
|
||||||
pip install $(ls /moto/dist/moto*.gz)[server,all]
|
pip install $(ls /moto/dist/moto*.gz)[server,all]
|
||||||
moto_server -H 0.0.0.0 -p 5000
|
moto_server -H 0.0.0.0 -p 5000
|
||||||
|
Loading…
x
Reference in New Issue
Block a user