Merge remote-tracking branch 'upstream/master'

This commit is contained in:
Stephan Huber 2020-01-23 15:37:40 +01:00
commit 84210f6011
78 changed files with 1527 additions and 635 deletions

View File

@ -1240,14 +1240,14 @@
- [ ] create_commit
- [ ] create_pull_request
- [ ] create_pull_request_approval_rule
- [ ] create_repository
- [X] create_repository
- [ ] create_unreferenced_merge_commit
- [ ] delete_approval_rule_template
- [ ] delete_branch
- [ ] delete_comment_content
- [ ] delete_file
- [ ] delete_pull_request_approval_rule
- [ ] delete_repository
- [X] delete_repository
- [ ] describe_merge_conflicts
- [ ] describe_pull_request_events
- [ ] disassociate_approval_rule_template_from_repository
@ -1268,7 +1268,7 @@
- [ ] get_pull_request
- [ ] get_pull_request_approval_states
- [ ] get_pull_request_override_state
- [ ] get_repository
- [X] get_repository
- [ ] get_repository_triggers
- [ ] list_approval_rule_templates
- [ ] list_associated_approval_rule_templates_for_repository
@ -1374,7 +1374,7 @@
- [ ] update_profiling_group
## codepipeline
13% implemented
22% implemented
- [ ] acknowledge_job
- [ ] acknowledge_third_party_job
- [ ] create_custom_action_type
@ -1394,7 +1394,7 @@
- [ ] list_action_types
- [ ] list_pipeline_executions
- [X] list_pipelines
- [ ] list_tags_for_resource
- [X] list_tags_for_resource
- [ ] list_webhooks
- [ ] poll_for_jobs
- [ ] poll_for_third_party_jobs
@ -1408,8 +1408,8 @@
- [ ] register_webhook_with_third_party
- [ ] retry_stage_execution
- [ ] start_pipeline_execution
- [ ] tag_resource
- [ ] untag_resource
- [X] tag_resource
- [X] untag_resource
- [X] update_pipeline
## codestar
@ -2694,7 +2694,7 @@
## ec2-instance-connect
0% implemented
- [ ] send_ssh_public_key
- [x] send_ssh_public_key
## ecr
27% implemented
@ -3763,7 +3763,7 @@
- [X] list_signing_certificates
- [ ] list_ssh_public_keys
- [X] list_user_policies
- [ ] list_user_tags
- [X] list_user_tags
- [X] list_users
- [X] list_virtual_mfa_devices
- [X] put_group_policy

View File

@ -283,14 +283,14 @@ def test_describe_instances_allowed():
]
}
access_key = ...
# create access key for an IAM user/assumed role that has the policy above.
# create access key for an IAM user/assumed role that has the policy above.
# this part should call __exactly__ 4 AWS actions, so that authentication and authorization starts exactly after this
client = boto3.client('ec2', region_name='us-east-1',
aws_access_key_id=access_key['AccessKeyId'],
aws_secret_access_key=access_key['SecretAccessKey'])
# if the IAM principal whose access key is used, does not have the permission to describe instances, this will fail
# if the IAM principal whose access key is used, does not have the permission to describe instances, this will fail
instances = client.describe_instances()['Reservations'][0]['Instances']
assert len(instances) == 0
```
@ -310,16 +310,16 @@ You need to ensure that the mocks are actually in place. Changes made to recent
have altered some of the mock behavior. In short, you need to ensure that you _always_ do the following:
1. Ensure that your tests have dummy environment variables set up:
export AWS_ACCESS_KEY_ID='testing'
export AWS_SECRET_ACCESS_KEY='testing'
export AWS_SECURITY_TOKEN='testing'
export AWS_SESSION_TOKEN='testing'
1. __VERY IMPORTANT__: ensure that you have your mocks set up __BEFORE__ your `boto3` client is established.
1. __VERY IMPORTANT__: ensure that you have your mocks set up __BEFORE__ your `boto3` client is established.
This can typically happen if you import a module that has a `boto3` client instantiated outside of a function.
See the pesky imports section below on how to work around this.
### Example on usage?
If you are a user of [pytest](https://pytest.org/en/latest/), you can leverage [pytest fixtures](https://pytest.org/en/latest/fixture.html#fixture)
to help set up your mocks and other AWS resources that you would need.
@ -354,7 +354,7 @@ def cloudwatch(aws_credentials):
... etc.
```
In the code sample above, all of the AWS/mocked fixtures take in a parameter of `aws_credentials`,
In the code sample above, all of the AWS/mocked fixtures take in a parameter of `aws_credentials`,
which sets the proper fake environment variables. The fake environment variables are used so that `botocore` doesn't try to locate real
credentials on your system.
@ -364,7 +364,7 @@ def test_create_bucket(s3):
# s3 is a fixture defined above that yields a boto3 s3 client.
# Feel free to instantiate another boto3 S3 client -- Keep note of the region though.
s3.create_bucket(Bucket="somebucket")
result = s3.list_buckets()
assert len(result['Buckets']) == 1
assert result['Buckets'][0]['Name'] == 'somebucket'
@ -373,7 +373,7 @@ def test_create_bucket(s3):
### What about those pesky imports?
Recall earlier, it was mentioned that mocks should be established __BEFORE__ the clients are set up. One way
to avoid import issues is to make use of local Python imports -- i.e. import the module inside of the unit
test you want to run vs. importing at the top of the file.
test you want to run vs. importing at the top of the file.
Example:
```python
@ -381,12 +381,12 @@ def test_something(s3):
from some.package.that.does.something.with.s3 import some_func # <-- Local import for unit test
# ^^ Importing here ensures that the mock has been established.
sume_func() # The mock has been established from the "s3" pytest fixture, so this function that uses
some_func() # The mock has been established from the "s3" pytest fixture, so this function that uses
# a package-level S3 client will properly use the mock and not reach out to AWS.
```
### Other caveats
For Tox, Travis CI, and other build systems, you might need to also perform a `touch ~/.aws/credentials`
For Tox, Travis CI, and other build systems, you might need to also perform a `touch ~/.aws/credentials`
command before running the tests. As long as that file is present (empty preferably) and the environment
variables above are set, you should be good to go.

View File

@ -76,7 +76,7 @@ Currently implemented Services:
+---------------------------+-----------------------+------------------------------------+
| Logs | @mock_logs | basic endpoints done |
+---------------------------+-----------------------+------------------------------------+
| Organizations | @mock_organizations | some core edpoints done |
| Organizations | @mock_organizations | some core endpoints done |
+---------------------------+-----------------------+------------------------------------+
| Polly | @mock_polly | all endpoints done |
+---------------------------+-----------------------+------------------------------------+

View File

@ -9,6 +9,7 @@ from .batch import mock_batch # noqa
from .cloudformation import mock_cloudformation # noqa
from .cloudformation import mock_cloudformation_deprecated # noqa
from .cloudwatch import mock_cloudwatch, mock_cloudwatch_deprecated # noqa
from .codecommit import mock_codecommit # noqa
from .codepipeline import mock_codepipeline # noqa
from .cognitoidentity import mock_cognitoidentity # noqa
from .cognitoidentity import mock_cognitoidentity_deprecated # noqa
@ -21,6 +22,7 @@ from .dynamodb import mock_dynamodb, mock_dynamodb_deprecated # noqa
from .dynamodb2 import mock_dynamodb2, mock_dynamodb2_deprecated # noqa
from .dynamodbstreams import mock_dynamodbstreams # noqa
from .ec2 import mock_ec2, mock_ec2_deprecated # noqa
from .ec2_instance_connect import mock_ec2_instance_connect # noqa
from .ecr import mock_ecr, mock_ecr_deprecated # noqa
from .ecs import mock_ecs, mock_ecs_deprecated # noqa
from .elb import mock_elb, mock_elb_deprecated # noqa

View File

@ -847,3 +847,11 @@ class APIGatewayBackend(BaseBackend):
apigateway_backends = {}
for region_name in Session().get_available_regions("apigateway"):
apigateway_backends[region_name] = APIGatewayBackend(region_name)
for region_name in Session().get_available_regions(
"apigateway", partition_name="aws-us-gov"
):
apigateway_backends[region_name] = APIGatewayBackend(region_name)
for region_name in Session().get_available_regions(
"apigateway", partition_name="aws-cn"
):
apigateway_backends[region_name] = APIGatewayBackend(region_name)

View File

@ -1,7 +1,8 @@
from __future__ import unicode_literals
import time
import boto3
from boto3 import Session
from moto.core import BaseBackend, BaseModel
from moto.core import ACCOUNT_ID
@ -77,5 +78,9 @@ class AthenaBackend(BaseBackend):
athena_backends = {}
for region in boto3.Session().get_available_regions("athena"):
for region in Session().get_available_regions("athena"):
athena_backends[region] = AthenaBackend(region)
for region in Session().get_available_regions("athena", partition_name="aws-us-gov"):
athena_backends[region] = AthenaBackend(region)
for region in Session().get_available_regions("athena", partition_name="aws-cn"):
athena_backends[region] = AthenaBackend(region)

View File

@ -23,7 +23,8 @@ import traceback
import weakref
import requests.adapters
import boto.awslambda
from boto3 import Session
from moto.core import BaseBackend, BaseModel
from moto.core.exceptions import RESTError
from moto.iam.models import iam_backend
@ -52,9 +53,6 @@ try:
except ImportError:
from backports.tempfile import TemporaryDirectory
# The lambci container is returning a special escape character for the "RequestID" fields. Unicode 033:
# _stderr_regex = re.compile(r"START|END|REPORT RequestId: .*")
_stderr_regex = re.compile(r"\033\[\d+.*")
_orig_adapter_send = requests.adapters.HTTPAdapter.send
docker_3 = docker.__version__[0] >= "3"
@ -384,7 +382,7 @@ class LambdaFunction(BaseModel):
try:
# TODO: I believe we can keep the container running and feed events as needed
# also need to hook it up to the other services so it can make kws/s3 etc calls
# Should get invoke_id /RequestId from invovation
# Should get invoke_id /RequestId from invocation
env_vars = {
"AWS_LAMBDA_FUNCTION_TIMEOUT": self.timeout,
"AWS_LAMBDA_FUNCTION_NAME": self.function_name,
@ -452,14 +450,9 @@ class LambdaFunction(BaseModel):
if exit_code != 0:
raise Exception("lambda invoke failed output: {}".format(output))
# strip out RequestId lines (TODO: This will return an additional '\n' in the response)
output = os.linesep.join(
[
line
for line in self.convert(output).splitlines()
if not _stderr_regex.match(line)
]
)
# We only care about the response from the lambda
# Which is the last line of the output, according to https://github.com/lambci/docker-lambda/issues/25
output = output.splitlines()[-1]
return output, False
except BaseException as e:
traceback.print_exc()
@ -1043,10 +1036,10 @@ def do_validate_s3():
return os.environ.get("VALIDATE_LAMBDA_S3", "") in ["", "1", "true"]
# Handle us forgotten regions, unless Lambda truly only runs out of US and
lambda_backends = {
_region.name: LambdaBackend(_region.name) for _region in boto.awslambda.regions()
}
lambda_backends["ap-southeast-2"] = LambdaBackend("ap-southeast-2")
lambda_backends["us-gov-west-1"] = LambdaBackend("us-gov-west-1")
lambda_backends = {}
for region in Session().get_available_regions("lambda"):
lambda_backends[region] = LambdaBackend(region)
for region in Session().get_available_regions("lambda", partition_name="aws-us-gov"):
lambda_backends[region] = LambdaBackend(region)
for region in Session().get_available_regions("lambda", partition_name="aws-cn"):
lambda_backends[region] = LambdaBackend(region)

View File

@ -8,6 +8,7 @@ from moto.awslambda import lambda_backends
from moto.batch import batch_backends
from moto.cloudformation import cloudformation_backends
from moto.cloudwatch import cloudwatch_backends
from moto.codecommit import codecommit_backends
from moto.codepipeline import codepipeline_backends
from moto.cognitoidentity import cognitoidentity_backends
from moto.cognitoidp import cognitoidp_backends
@ -19,6 +20,7 @@ from moto.dynamodb import dynamodb_backends
from moto.dynamodb2 import dynamodb_backends2
from moto.dynamodbstreams import dynamodbstreams_backends
from moto.ec2 import ec2_backends
from moto.ec2_instance_connect import ec2_instance_connect_backends
from moto.ecr import ecr_backends
from moto.ecs import ecs_backends
from moto.elb import elb_backends
@ -61,6 +63,7 @@ BACKENDS = {
"batch": batch_backends,
"cloudformation": cloudformation_backends,
"cloudwatch": cloudwatch_backends,
"codecommit": codecommit_backends,
"codepipeline": codepipeline_backends,
"cognito-identity": cognitoidentity_backends,
"cognito-idp": cognitoidp_backends,
@ -71,6 +74,7 @@ BACKENDS = {
"dynamodb2": dynamodb_backends2,
"dynamodbstreams": dynamodbstreams_backends,
"ec2": ec2_backends,
"ec2_instance_connect": ec2_instance_connect_backends,
"ecr": ecr_backends,
"ecs": ecs_backends,
"elb": elb_backends,

View File

@ -1,5 +1,4 @@
from __future__ import unicode_literals
import boto3
import re
import requests.adapters
from itertools import cycle
@ -12,6 +11,8 @@ import docker
import functools
import threading
import dateutil.parser
from boto3 import Session
from moto.core import BaseBackend, BaseModel
from moto.iam import iam_backends
from moto.ec2 import ec2_backends
@ -1317,7 +1318,10 @@ class BatchBackend(BaseBackend):
job.terminate(reason)
available_regions = boto3.session.Session().get_available_regions("batch")
batch_backends = {
region: BatchBackend(region_name=region) for region in available_regions
}
batch_backends = {}
for region in Session().get_available_regions("batch"):
batch_backends[region] = BatchBackend(region)
for region in Session().get_available_regions("batch", partition_name="aws-us-gov"):
batch_backends[region] = BatchBackend(region)
for region in Session().get_available_regions("batch", partition_name="aws-cn"):
batch_backends[region] = BatchBackend(region)

View File

@ -4,7 +4,8 @@ import json
import yaml
import uuid
import boto.cloudformation
from boto3 import Session
from moto.compat import OrderedDict
from moto.core import BaseBackend, BaseModel
@ -717,5 +718,13 @@ class CloudFormationBackend(BaseBackend):
cloudformation_backends = {}
for region in boto.cloudformation.regions():
cloudformation_backends[region.name] = CloudFormationBackend()
for region in Session().get_available_regions("cloudformation"):
cloudformation_backends[region] = CloudFormationBackend()
for region in Session().get_available_regions(
"cloudformation", partition_name="aws-us-gov"
):
cloudformation_backends[region] = CloudFormationBackend()
for region in Session().get_available_regions(
"cloudformation", partition_name="aws-cn"
):
cloudformation_backends[region] = CloudFormationBackend()

View File

@ -1,8 +1,10 @@
import json
from boto3 import Session
from moto.core.utils import iso_8601_datetime_with_milliseconds
from moto.core import BaseBackend, BaseModel
from moto.core.exceptions import RESTError
import boto.ec2.cloudwatch
from datetime import datetime, timedelta
from dateutil.tz import tzutc
from uuid import uuid4
@ -431,5 +433,11 @@ class LogGroup(BaseModel):
cloudwatch_backends = {}
for region in boto.ec2.cloudwatch.regions():
cloudwatch_backends[region.name] = CloudWatchBackend()
for region in Session().get_available_regions("cloudwatch"):
cloudwatch_backends[region] = CloudWatchBackend()
for region in Session().get_available_regions(
"cloudwatch", partition_name="aws-us-gov"
):
cloudwatch_backends[region] = CloudWatchBackend()
for region in Session().get_available_regions("cloudwatch", partition_name="aws-cn"):
cloudwatch_backends[region] = CloudWatchBackend()

View File

@ -0,0 +1,4 @@
from .models import codecommit_backends
from ..core.models import base_decorator
mock_codecommit = base_decorator(codecommit_backends)

View File

@ -0,0 +1,35 @@
from moto.core.exceptions import JsonRESTError
class RepositoryNameExistsException(JsonRESTError):
code = 400
def __init__(self, repository_name):
super(RepositoryNameExistsException, self).__init__(
"RepositoryNameExistsException",
"Repository named {0} already exists".format(repository_name),
)
class RepositoryDoesNotExistException(JsonRESTError):
code = 400
def __init__(self, repository_name):
super(RepositoryDoesNotExistException, self).__init__(
"RepositoryDoesNotExistException",
"{0} does not exist".format(repository_name),
)
class InvalidRepositoryNameException(JsonRESTError):
code = 400
def __init__(self):
super(InvalidRepositoryNameException, self).__init__(
"InvalidRepositoryNameException",
"The repository name is not valid. Repository names can be any valid "
"combination of letters, numbers, "
"periods, underscores, and dashes between 1 and 100 characters in "
"length. Names are case sensitive. "
"For more information, see Limits in the AWS CodeCommit User Guide. ",
)

69
moto/codecommit/models.py Normal file
View File

@ -0,0 +1,69 @@
from boto3 import Session
from moto.core import BaseBackend, BaseModel
from moto.core.utils import iso_8601_datetime_with_milliseconds
from datetime import datetime
from moto.iam.models import ACCOUNT_ID
from .exceptions import RepositoryDoesNotExistException, RepositoryNameExistsException
import uuid
class CodeCommit(BaseModel):
def __init__(self, region, repository_description, repository_name):
current_date = iso_8601_datetime_with_milliseconds(datetime.utcnow())
self.repository_metadata = dict()
self.repository_metadata["repositoryName"] = repository_name
self.repository_metadata[
"cloneUrlSsh"
] = "ssh://git-codecommit.{0}.amazonaws.com/v1/repos/{1}".format(
region, repository_name
)
self.repository_metadata[
"cloneUrlHttp"
] = "https://git-codecommit.{0}.amazonaws.com/v1/repos/{1}".format(
region, repository_name
)
self.repository_metadata["creationDate"] = current_date
self.repository_metadata["lastModifiedDate"] = current_date
self.repository_metadata["repositoryDescription"] = repository_description
self.repository_metadata["repositoryId"] = str(uuid.uuid4())
self.repository_metadata["Arn"] = "arn:aws:codecommit:{0}:{1}:{2}".format(
region, ACCOUNT_ID, repository_name
)
self.repository_metadata["accountId"] = ACCOUNT_ID
class CodeCommitBackend(BaseBackend):
def __init__(self):
self.repositories = {}
def create_repository(self, region, repository_name, repository_description):
repository = self.repositories.get(repository_name)
if repository:
raise RepositoryNameExistsException(repository_name)
self.repositories[repository_name] = CodeCommit(
region, repository_description, repository_name
)
return self.repositories[repository_name].repository_metadata
def get_repository(self, repository_name):
repository = self.repositories.get(repository_name)
if not repository:
raise RepositoryDoesNotExistException(repository_name)
return repository.repository_metadata
def delete_repository(self, repository_name):
repository = self.repositories.get(repository_name)
if repository:
self.repositories.pop(repository_name)
return repository.repository_metadata.get("repositoryId")
return None
codecommit_backends = {}
for region in Session().get_available_regions("codecommit"):
codecommit_backends[region] = CodeCommitBackend()

View File

@ -0,0 +1,57 @@
import json
import re
from moto.core.responses import BaseResponse
from .models import codecommit_backends
from .exceptions import InvalidRepositoryNameException
def _is_repository_name_valid(repository_name):
name_regex = re.compile(r"[\w\.-]+")
result = name_regex.split(repository_name)
if len(result) > 0:
for match in result:
if len(match) > 0:
return False
return True
class CodeCommitResponse(BaseResponse):
@property
def codecommit_backend(self):
return codecommit_backends[self.region]
def create_repository(self):
if not _is_repository_name_valid(self._get_param("repositoryName")):
raise InvalidRepositoryNameException()
repository_metadata = self.codecommit_backend.create_repository(
self.region,
self._get_param("repositoryName"),
self._get_param("repositoryDescription"),
)
return json.dumps({"repositoryMetadata": repository_metadata})
def get_repository(self):
if not _is_repository_name_valid(self._get_param("repositoryName")):
raise InvalidRepositoryNameException()
repository_metadata = self.codecommit_backend.get_repository(
self._get_param("repositoryName")
)
return json.dumps({"repositoryMetadata": repository_metadata})
def delete_repository(self):
if not _is_repository_name_valid(self._get_param("repositoryName")):
raise InvalidRepositoryNameException()
repository_id = self.codecommit_backend.delete_repository(
self._get_param("repositoryName")
)
if repository_id:
return json.dumps({"repositoryId": repository_id})
return json.dumps({})

6
moto/codecommit/urls.py Normal file
View File

@ -0,0 +1,6 @@
from __future__ import unicode_literals
from .responses import CodeCommitResponse
url_bases = ["https?://codecommit.(.+).amazonaws.com"]
url_paths = {"{0}/$": CodeCommitResponse.dispatch}

View File

@ -26,3 +26,19 @@ class ResourceNotFoundException(JsonRESTError):
super(ResourceNotFoundException, self).__init__(
"ResourceNotFoundException", message
)
class InvalidTagsException(JsonRESTError):
code = 400
def __init__(self, message):
super(InvalidTagsException, self).__init__("InvalidTagsException", message)
class TooManyTagsException(JsonRESTError):
code = 400
def __init__(self, arn):
super(TooManyTagsException, self).__init__(
"TooManyTagsException", "Tag limit exceeded for resource [{}].".format(arn)
)

View File

@ -12,6 +12,8 @@ from moto.codepipeline.exceptions import (
InvalidStructureException,
PipelineNotFoundException,
ResourceNotFoundException,
InvalidTagsException,
TooManyTagsException,
)
from moto.core import BaseBackend, BaseModel
@ -54,6 +56,18 @@ class CodePipeline(BaseModel):
return pipeline
def validate_tags(self, tags):
for tag in tags:
if tag["key"].startswith("aws:"):
raise InvalidTagsException(
"Not allowed to modify system tags. "
"System tags start with 'aws:'. "
"msg=[Caller is an end user and not allowed to mutate system tags]"
)
if (len(self.tags) + len(tags)) > 50:
raise TooManyTagsException(self._arn)
class CodePipelineBackend(BaseBackend):
def __init__(self):
@ -93,10 +107,12 @@ class CodePipelineBackend(BaseBackend):
self.pipelines[pipeline["name"]] = CodePipeline(region, pipeline)
if tags:
self.pipelines[pipeline["name"]].validate_tags(tags)
new_tags = {tag["key"]: tag["value"] for tag in tags}
self.pipelines[pipeline["name"]].tags.update(new_tags)
return pipeline, tags
return pipeline, sorted(tags, key=lambda i: i["key"])
def get_pipeline(self, name):
codepipeline = self.pipelines.get(name)
@ -145,7 +161,58 @@ class CodePipelineBackend(BaseBackend):
def delete_pipeline(self, name):
self.pipelines.pop(name, None)
def list_tags_for_resource(self, arn):
name = arn.split(":")[-1]
pipeline = self.pipelines.get(name)
if not pipeline:
raise ResourceNotFoundException(
"The account with id '{0}' does not include a pipeline with the name '{1}'".format(
ACCOUNT_ID, name
)
)
tags = [{"key": key, "value": value} for key, value in pipeline.tags.items()]
return sorted(tags, key=lambda i: i["key"])
def tag_resource(self, arn, tags):
name = arn.split(":")[-1]
pipeline = self.pipelines.get(name)
if not pipeline:
raise ResourceNotFoundException(
"The account with id '{0}' does not include a pipeline with the name '{1}'".format(
ACCOUNT_ID, name
)
)
pipeline.validate_tags(tags)
for tag in tags:
pipeline.tags.update({tag["key"]: tag["value"]})
def untag_resource(self, arn, tag_keys):
name = arn.split(":")[-1]
pipeline = self.pipelines.get(name)
if not pipeline:
raise ResourceNotFoundException(
"The account with id '{0}' does not include a pipeline with the name '{1}'".format(
ACCOUNT_ID, name
)
)
for key in tag_keys:
pipeline.tags.pop(key, None)
codepipeline_backends = {}
for region in Session().get_available_regions("codepipeline"):
codepipeline_backends[region] = CodePipelineBackend()
for region in Session().get_available_regions(
"codepipeline", partition_name="aws-us-gov"
):
codepipeline_backends[region] = CodePipelineBackend()
for region in Session().get_available_regions("codepipeline", partition_name="aws-cn"):
codepipeline_backends[region] = CodePipelineBackend()

View File

@ -39,3 +39,24 @@ class CodePipelineResponse(BaseResponse):
self.codepipeline_backend.delete_pipeline(self._get_param("name"))
return ""
def list_tags_for_resource(self):
tags = self.codepipeline_backend.list_tags_for_resource(
self._get_param("resourceArn")
)
return json.dumps({"tags": tags})
def tag_resource(self):
self.codepipeline_backend.tag_resource(
self._get_param("resourceArn"), self._get_param("tags")
)
return ""
def untag_resource(self):
self.codepipeline_backend.untag_resource(
self._get_param("resourceArn"), self._get_param("tagKeys")
)
return ""

View File

@ -3,7 +3,7 @@ from __future__ import unicode_literals
import datetime
import json
import boto.cognito.identity
from boto3 import Session
from moto.compat import OrderedDict
from moto.core import BaseBackend, BaseModel
@ -136,5 +136,13 @@ class CognitoIdentityBackend(BaseBackend):
cognitoidentity_backends = {}
for region in boto.cognito.identity.regions():
cognitoidentity_backends[region.name] = CognitoIdentityBackend(region.name)
for region in Session().get_available_regions("cognito-identity"):
cognitoidentity_backends[region] = CognitoIdentityBackend(region)
for region in Session().get_available_regions(
"cognito-identity", partition_name="aws-us-gov"
):
cognitoidentity_backends[region] = CognitoIdentityBackend(region)
for region in Session().get_available_regions(
"cognito-identity", partition_name="aws-cn"
):
cognitoidentity_backends[region] = CognitoIdentityBackend(region)

View File

@ -9,7 +9,7 @@ import os
import time
import uuid
import boto.cognito.identity
from boto3 import Session
from jose import jws
from moto.compat import OrderedDict
@ -108,7 +108,9 @@ class CognitoIdpUserPool(BaseModel):
return user_pool_json
def create_jwt(self, client_id, username, expires_in=60 * 60, extra_data={}):
def create_jwt(
self, client_id, username, token_use, expires_in=60 * 60, extra_data={}
):
now = int(time.time())
payload = {
"iss": "https://cognito-idp.{}.amazonaws.com/{}".format(
@ -116,7 +118,7 @@ class CognitoIdpUserPool(BaseModel):
),
"sub": self.users[username].id,
"aud": client_id,
"token_use": "id",
"token_use": token_use,
"auth_time": now,
"exp": now + expires_in,
}
@ -125,7 +127,10 @@ class CognitoIdpUserPool(BaseModel):
return jws.sign(payload, self.json_web_key, algorithm="RS256"), expires_in
def create_id_token(self, client_id, username):
id_token, expires_in = self.create_jwt(client_id, username)
extra_data = self.get_user_extra_data_by_client_id(client_id, username)
id_token, expires_in = self.create_jwt(
client_id, username, "id", extra_data=extra_data
)
self.id_tokens[id_token] = (client_id, username)
return id_token, expires_in
@ -135,10 +140,7 @@ class CognitoIdpUserPool(BaseModel):
return refresh_token
def create_access_token(self, client_id, username):
extra_data = self.get_user_extra_data_by_client_id(client_id, username)
access_token, expires_in = self.create_jwt(
client_id, username, extra_data=extra_data
)
access_token, expires_in = self.create_jwt(client_id, username, "access")
self.access_tokens[access_token] = (client_id, username)
return access_token, expires_in
@ -749,8 +751,14 @@ class CognitoIdpBackend(BaseBackend):
cognitoidp_backends = {}
for region in boto.cognito.identity.regions():
cognitoidp_backends[region.name] = CognitoIdpBackend(region.name)
for region in Session().get_available_regions("cognito-idp"):
cognitoidp_backends[region] = CognitoIdpBackend(region)
for region in Session().get_available_regions(
"cognito-idp", partition_name="aws-us-gov"
):
cognitoidp_backends[region] = CognitoIdpBackend(region)
for region in Session().get_available_regions("cognito-idp", partition_name="aws-cn"):
cognitoidp_backends[region] = CognitoIdpBackend(region)
# Hack to help moto-server process requests on localhost, where the region isn't

View File

@ -1084,6 +1084,9 @@ class ConfigBackend(BaseBackend):
config_backends = {}
boto3_session = Session()
for region in boto3_session.get_available_regions("config"):
for region in Session().get_available_regions("config"):
config_backends[region] = ConfigBackend()
for region in Session().get_available_regions("config", partition_name="aws-us-gov"):
config_backends[region] = ConfigBackend()
for region in Session().get_available_regions("config", partition_name="aws-cn"):
config_backends[region] = ConfigBackend()

View File

@ -1,7 +1,8 @@
from __future__ import unicode_literals
import datetime
import boto.datapipeline
from boto3 import Session
from moto.compat import OrderedDict
from moto.core import BaseBackend, BaseModel
from .utils import get_random_pipeline_id, remove_capitalization_of_dict_keys
@ -142,5 +143,11 @@ class DataPipelineBackend(BaseBackend):
datapipeline_backends = {}
for region in boto.datapipeline.regions():
datapipeline_backends[region.name] = DataPipelineBackend()
for region in Session().get_available_regions("datapipeline"):
datapipeline_backends[region] = DataPipelineBackend()
for region in Session().get_available_regions(
"datapipeline", partition_name="aws-us-gov"
):
datapipeline_backends[region] = DataPipelineBackend()
for region in Session().get_available_regions("datapipeline", partition_name="aws-cn"):
datapipeline_backends[region] = DataPipelineBackend(region)

View File

@ -1,4 +1,5 @@
import boto3
from boto3 import Session
from moto.compat import OrderedDict
from moto.core import BaseBackend, BaseModel
@ -226,5 +227,9 @@ class DataSyncBackend(BaseBackend):
datasync_backends = {}
for region in boto3.Session().get_available_regions("datasync"):
datasync_backends[region] = DataSyncBackend(region_name=region)
for region in Session().get_available_regions("datasync"):
datasync_backends[region] = DataSyncBackend(region)
for region in Session().get_available_regions("datasync", partition_name="aws-us-gov"):
datasync_backends[region] = DataSyncBackend(region)
for region in Session().get_available_regions("datasync", partition_name="aws-cn"):
datasync_backends[region] = DataSyncBackend(region)

View File

@ -977,10 +977,8 @@ class OpLessThan(Op):
lhs = self.lhs.expr(item)
rhs = self.rhs.expr(item)
# In python3 None is not a valid comparator when using < or > so must be handled specially
if lhs and rhs:
if lhs is not None and rhs is not None:
return lhs < rhs
elif lhs is None and rhs:
return True
else:
return False
@ -992,10 +990,8 @@ class OpGreaterThan(Op):
lhs = self.lhs.expr(item)
rhs = self.rhs.expr(item)
# In python3 None is not a valid comparator when using < or > so must be handled specially
if lhs and rhs:
if lhs is not None and rhs is not None:
return lhs > rhs
elif lhs and rhs is None:
return True
else:
return False
@ -1025,10 +1021,8 @@ class OpLessThanOrEqual(Op):
lhs = self.lhs.expr(item)
rhs = self.rhs.expr(item)
# In python3 None is not a valid comparator when using < or > so must be handled specially
if lhs and rhs:
if lhs is not None and rhs is not None:
return lhs <= rhs
elif lhs is None and rhs or lhs is None and rhs is None:
return True
else:
return False
@ -1040,10 +1034,8 @@ class OpGreaterThanOrEqual(Op):
lhs = self.lhs.expr(item)
rhs = self.rhs.expr(item)
# In python3 None is not a valid comparator when using < or > so must be handled specially
if lhs and rhs:
if lhs is not None and rhs is not None:
return lhs >= rhs
elif lhs and rhs is None or lhs is None and rhs is None:
return True
else:
return False

View File

@ -8,7 +8,7 @@ import re
import uuid
import six
import boto3
from boto3 import Session
from botocore.exceptions import ParamValidationError
from moto.compat import OrderedDict
from moto.core import BaseBackend, BaseModel
@ -457,7 +457,7 @@ class Item(BaseModel):
)
if not old_list.is_list():
raise ParamValidationError
old_list.value.extend(new_value["L"])
old_list.value.extend([DynamoType(v) for v in new_value["L"]])
value = old_list
return value
@ -586,7 +586,9 @@ class StreamRecord(BaseModel):
self.record["dynamodb"]["OldImage"] = old_a
# This is a substantial overestimate but it's the easiest to do now
self.record["dynamodb"]["SizeBytes"] = len(json.dumps(self.record["dynamodb"]))
self.record["dynamodb"]["SizeBytes"] = len(
dynamo_json_dump(self.record["dynamodb"])
)
def to_json(self):
return self.record
@ -1484,7 +1486,10 @@ class DynamoDBBackend(BaseBackend):
return table.ttl
available_regions = boto3.session.Session().get_available_regions("dynamodb")
dynamodb_backends = {
region: DynamoDBBackend(region_name=region) for region in available_regions
}
dynamodb_backends = {}
for region in Session().get_available_regions("dynamodb"):
dynamodb_backends[region] = DynamoDBBackend(region)
for region in Session().get_available_regions("dynamodb", partition_name="aws-us-gov"):
dynamodb_backends[region] = DynamoDBBackend(region)
for region in Session().get_available_regions("dynamodb", partition_name="aws-cn"):
dynamodb_backends[region] = DynamoDBBackend(region)

View File

@ -2,9 +2,10 @@ from __future__ import unicode_literals
import os
import json
import boto3
import base64
from boto3 import Session
from moto.core import BaseBackend, BaseModel
from moto.dynamodb2.models import dynamodb_backends
@ -139,7 +140,14 @@ class DynamoDBStreamsBackend(BaseBackend):
return json.dumps(shard_iterator.get(limit))
available_regions = boto3.session.Session().get_available_regions("dynamodbstreams")
dynamodbstreams_backends = {
region: DynamoDBStreamsBackend(region=region) for region in available_regions
}
dynamodbstreams_backends = {}
for region in Session().get_available_regions("dynamodbstreams"):
dynamodbstreams_backends[region] = DynamoDBStreamsBackend(region)
for region in Session().get_available_regions(
"dynamodbstreams", partition_name="aws-us-gov"
):
dynamodbstreams_backends[region] = DynamoDBStreamsBackend(region)
for region in Session().get_available_regions(
"dynamodbstreams", partition_name="aws-cn"
):
dynamodbstreams_backends[region] = DynamoDBStreamsBackend(region)

View File

@ -8,9 +8,9 @@ import os
import re
import six
import warnings
from pkg_resources import resource_filename
import boto.ec2
from boto3 import Session
from pkg_resources import resource_filename
from collections import defaultdict
import weakref
@ -1473,7 +1473,13 @@ class Zone(object):
class RegionsAndZonesBackend(object):
regions = [Region(ri.name, ri.endpoint) for ri in boto.ec2.regions()]
regions = []
for region in Session().get_available_regions("ec2"):
regions.append(Region(region, "ec2.{}.amazonaws.com".format(region)))
for region in Session().get_available_regions("ec2", partition_name="aws-us-gov"):
regions.append(Region(region, "ec2.{}.amazonaws.com".format(region)))
for region in Session().get_available_regions("ec2", partition_name="aws-cn"):
regions.append(Region(region, "ec2.{}.amazonaws.com.cn".format(region)))
zones = {
"ap-south-1": [
@ -1536,6 +1542,11 @@ class RegionsAndZonesBackend(object):
zone_id="apne1-az2",
),
],
"ap-east-1": [
Zone(region_name="ap-east-1", name="ap-east-1a", zone_id="ape1-az1"),
Zone(region_name="ap-east-1", name="ap-east-1b", zone_id="ape1-az2"),
Zone(region_name="ap-east-1", name="ap-east-1c", zone_id="ape1-az3"),
],
"sa-east-1": [
Zone(region_name="sa-east-1", name="sa-east-1a", zone_id="sae1-az1"),
Zone(region_name="sa-east-1", name="sa-east-1c", zone_id="sae1-az3"),
@ -1605,10 +1616,32 @@ class RegionsAndZonesBackend(object):
Zone(region_name="us-west-2", name="us-west-2b", zone_id="usw2-az1"),
Zone(region_name="us-west-2", name="us-west-2c", zone_id="usw2-az3"),
],
"me-south-1": [
Zone(region_name="me-south-1", name="me-south-1a", zone_id="mes1-az1"),
Zone(region_name="me-south-1", name="me-south-1b", zone_id="mes1-az2"),
Zone(region_name="me-south-1", name="me-south-1c", zone_id="mes1-az3"),
],
"cn-north-1": [
Zone(region_name="cn-north-1", name="cn-north-1a", zone_id="cnn1-az1"),
Zone(region_name="cn-north-1", name="cn-north-1b", zone_id="cnn1-az2"),
],
"cn-northwest-1": [
Zone(
region_name="cn-northwest-1",
name="cn-northwest-1a",
zone_id="cnnw1-az1",
),
Zone(
region_name="cn-northwest-1",
name="cn-northwest-1b",
zone_id="cnnw1-az2",
),
Zone(
region_name="cn-northwest-1",
name="cn-northwest-1c",
zone_id="cnnw1-az3",
),
],
"us-gov-west-1": [
Zone(
region_name="us-gov-west-1", name="us-gov-west-1a", zone_id="usgw1-az1"
@ -1620,6 +1653,17 @@ class RegionsAndZonesBackend(object):
region_name="us-gov-west-1", name="us-gov-west-1c", zone_id="usgw1-az3"
),
],
"us-gov-east-1": [
Zone(
region_name="us-gov-east-1", name="us-gov-east-1a", zone_id="usge1-az1"
),
Zone(
region_name="us-gov-east-1", name="us-gov-east-1b", zone_id="usge1-az2"
),
Zone(
region_name="us-gov-east-1", name="us-gov-east-1c", zone_id="usge1-az3"
),
],
}
def describe_regions(self, region_names=[]):

View File

@ -104,7 +104,7 @@ class SecurityGroups(BaseResponse):
if self.is_not_dryrun("GrantSecurityGroupIngress"):
for args in self._process_rules_from_querystring():
self.ec2_backend.authorize_security_group_ingress(*args)
return AUTHORIZE_SECURITY_GROUP_INGRESS_REPONSE
return AUTHORIZE_SECURITY_GROUP_INGRESS_RESPONSE
def create_security_group(self):
name = self._get_param("GroupName")
@ -158,7 +158,7 @@ class SecurityGroups(BaseResponse):
if self.is_not_dryrun("RevokeSecurityGroupIngress"):
for args in self._process_rules_from_querystring():
self.ec2_backend.revoke_security_group_ingress(*args)
return REVOKE_SECURITY_GROUP_INGRESS_REPONSE
return REVOKE_SECURITY_GROUP_INGRESS_RESPONSE
CREATE_SECURITY_GROUP_RESPONSE = """<CreateSecurityGroupResponse xmlns="http://ec2.amazonaws.com/doc/2013-10-15/">
@ -265,12 +265,12 @@ DESCRIBE_SECURITY_GROUPS_RESPONSE = (
</DescribeSecurityGroupsResponse>"""
)
AUTHORIZE_SECURITY_GROUP_INGRESS_REPONSE = """<AuthorizeSecurityGroupIngressResponse xmlns="http://ec2.amazonaws.com/doc/2013-10-15/">
AUTHORIZE_SECURITY_GROUP_INGRESS_RESPONSE = """<AuthorizeSecurityGroupIngressResponse xmlns="http://ec2.amazonaws.com/doc/2013-10-15/">
<requestId>59dbff89-35bd-4eac-99ed-be587EXAMPLE</requestId>
<return>true</return>
</AuthorizeSecurityGroupIngressResponse>"""
REVOKE_SECURITY_GROUP_INGRESS_REPONSE = """<RevokeSecurityGroupIngressResponse xmlns="http://ec2.amazonaws.com/doc/2013-10-15/">
REVOKE_SECURITY_GROUP_INGRESS_RESPONSE = """<RevokeSecurityGroupIngressResponse xmlns="http://ec2.amazonaws.com/doc/2013-10-15/">
<requestId>59dbff89-35bd-4eac-99ed-be587EXAMPLE</requestId>
<return>true</return>
</RevokeSecurityGroupIngressResponse>"""

View File

@ -2,6 +2,6 @@ from __future__ import unicode_literals
from .responses import EC2Response
url_bases = ["https?://ec2.(.+).amazonaws.com(|.cn)"]
url_bases = ["https?://ec2\.(.+)\.amazonaws\.com(|\.cn)"]
url_paths = {"{0}/": EC2Response.dispatch}

View File

@ -0,0 +1,4 @@
from ..core.models import base_decorator
from .models import ec2_instance_connect_backends
mock_ec2_instance_connect = base_decorator(ec2_instance_connect_backends)

View File

@ -0,0 +1,11 @@
import boto
from moto.core import BaseBackend
class Ec2InstanceConnectBackend(BaseBackend):
pass
ec2_instance_connect_backends = {}
for region in boto.ec2.regions():
ec2_instance_connect_backends[region.name] = Ec2InstanceConnectBackend()

View File

@ -0,0 +1,9 @@
import json
from moto.core.responses import BaseResponse
class Ec2InstanceConnectResponse(BaseResponse):
def send_ssh_public_key(self):
return json.dumps(
{"RequestId": "example-2a47-4c91-9700-e37e85162cb6", "Success": True}
)

View File

@ -0,0 +1,6 @@
from __future__ import unicode_literals
from .responses import Ec2InstanceConnectResponse
url_bases = ["https?://ec2-instance-connect\.(.+)\.amazonaws\.com"]
url_paths = {"{0}/$": Ec2InstanceConnectResponse.dispatch}

View File

@ -3,9 +3,10 @@ import re
import uuid
from datetime import datetime
from random import random, randint
import boto3
import pytz
from boto3 import Session
from moto.core.exceptions import JsonRESTError
from moto.core import BaseBackend, BaseModel
from moto.core.utils import unix_time
@ -117,6 +118,7 @@ class TaskDefinition(BaseObject):
revision,
container_definitions,
region_name,
network_mode=None,
volumes=None,
tags=None,
):
@ -131,6 +133,10 @@ class TaskDefinition(BaseObject):
self.volumes = []
else:
self.volumes = volumes
if network_mode is None:
self.network_mode = "bridge"
else:
self.network_mode = network_mode
@property
def response_object(self):
@ -552,7 +558,7 @@ class EC2ContainerServiceBackend(BaseBackend):
raise Exception("{0} is not a cluster".format(cluster_name))
def register_task_definition(
self, family, container_definitions, volumes, tags=None
self, family, container_definitions, volumes=None, network_mode=None, tags=None
):
if family in self.task_definitions:
last_id = self._get_last_task_definition_revision_id(family)
@ -561,7 +567,13 @@ class EC2ContainerServiceBackend(BaseBackend):
self.task_definitions[family] = {}
revision = 1
task_definition = TaskDefinition(
family, revision, container_definitions, self.region_name, volumes, tags
family,
revision,
container_definitions,
self.region_name,
volumes=volumes,
network_mode=network_mode,
tags=tags,
)
self.task_definitions[family][revision] = task_definition
@ -1302,7 +1314,10 @@ class EC2ContainerServiceBackend(BaseBackend):
raise NotImplementedError()
available_regions = boto3.session.Session().get_available_regions("ecs")
ecs_backends = {
region: EC2ContainerServiceBackend(region) for region in available_regions
}
ecs_backends = {}
for region in Session().get_available_regions("ecs"):
ecs_backends[region] = EC2ContainerServiceBackend(region)
for region in Session().get_available_regions("ecs", partition_name="aws-us-gov"):
ecs_backends[region] = EC2ContainerServiceBackend(region)
for region in Session().get_available_regions("ecs", partition_name="aws-cn"):
ecs_backends[region] = EC2ContainerServiceBackend(region)

View File

@ -62,8 +62,13 @@ class EC2ContainerServiceResponse(BaseResponse):
container_definitions = self._get_param("containerDefinitions")
volumes = self._get_param("volumes")
tags = self._get_param("tags")
network_mode = self._get_param("networkMode")
task_definition = self.ecs_backend.register_task_definition(
family, container_definitions, volumes, tags
family,
container_definitions,
volumes=volumes,
network_mode=network_mode,
tags=tags,
)
return json.dumps({"taskDefinition": task_definition.response_object})

View File

@ -2,8 +2,8 @@ from __future__ import unicode_literals
from datetime import datetime
from datetime import timedelta
import boto.emr
import pytz
from boto3 import Session
from dateutil.parser import parse as dtparse
from moto.core import BaseBackend, BaseModel
from moto.emr.exceptions import EmrError
@ -460,5 +460,9 @@ class ElasticMapReduceBackend(BaseBackend):
emr_backends = {}
for region in boto.emr.regions():
emr_backends[region.name] = ElasticMapReduceBackend(region.name)
for region in Session().get_available_regions("emr"):
emr_backends[region] = ElasticMapReduceBackend(region)
for region in Session().get_available_regions("emr", partition_name="aws-us-gov"):
emr_backends[region] = ElasticMapReduceBackend(region)
for region in Session().get_available_regions("emr", partition_name="aws-cn"):
emr_backends[region] = ElasticMapReduceBackend(region)

View File

@ -1,7 +1,7 @@
import os
import re
import json
import boto3
from boto3 import Session
from moto.core.exceptions import JsonRESTError
from moto.core import BaseBackend, BaseModel
@ -362,5 +362,10 @@ class EventsBackend(BaseBackend):
self.event_buses.pop(name, None)
available_regions = boto3.session.Session().get_available_regions("events")
events_backends = {region: EventsBackend(region) for region in available_regions}
events_backends = {}
for region in Session().get_available_regions("events"):
events_backends[region] = EventsBackend(region)
for region in Session().get_available_regions("events", partition_name="aws-us-gov"):
events_backends[region] = EventsBackend(region)
for region in Session().get_available_regions("events", partition_name="aws-cn"):
events_backends[region] = EventsBackend(region)

View File

@ -4,8 +4,8 @@ import hashlib
import datetime
from boto3 import Session
import boto.glacier
from moto.core import BaseBackend, BaseModel
from .utils import get_job_id
@ -221,5 +221,9 @@ class GlacierBackend(BaseBackend):
glacier_backends = {}
for region in boto.glacier.regions():
glacier_backends[region.name] = GlacierBackend(region)
for region in Session().get_available_regions("glacier"):
glacier_backends[region] = GlacierBackend(region)
for region in Session().get_available_regions("glacier", partition_name="aws-us-gov"):
glacier_backends[region] = GlacierBackend(region)
for region in Session().get_available_regions("glacier", partition_name="aws-cn"):
glacier_backends[region] = GlacierBackend(region)

View File

@ -543,7 +543,7 @@ class Group(BaseModel):
class User(BaseModel):
def __init__(self, name, path=None):
def __init__(self, name, path=None, tags=None):
self.name = name
self.id = random_resource_id()
self.path = path if path else "/"
@ -556,6 +556,7 @@ class User(BaseModel):
self.password = None
self.password_reset_required = False
self.signing_certificates = {}
self.tags = tags
@property
def arn(self):
@ -1421,13 +1422,13 @@ class IAMBackend(BaseBackend):
"The group with name {0} cannot be found.".format(group_name)
)
def create_user(self, user_name, path="/"):
def create_user(self, user_name, path="/", tags=None):
if user_name in self.users:
raise IAMConflictException(
"EntityAlreadyExists", "User {0} already exists".format(user_name)
)
user = User(user_name, path)
user = User(user_name, path, tags)
self.users[user_name] = user
return user
@ -1583,6 +1584,10 @@ class IAMBackend(BaseBackend):
user = self.get_user(user_name)
return user.policies.keys()
def list_user_tags(self, user_name):
user = self.get_user(user_name)
return user.tags
def put_user_policy(self, user_name, policy_name, policy_json):
user = self.get_user(user_name)

View File

@ -440,8 +440,8 @@ class IamResponse(BaseResponse):
def create_user(self):
user_name = self._get_param("UserName")
path = self._get_param("Path")
user = iam_backend.create_user(user_name, path)
tags = self._get_multi_param("Tags.member")
user = iam_backend.create_user(user_name, path, tags)
template = self.response_template(USER_TEMPLATE)
return template.render(action="Create", user=user)
@ -538,6 +538,12 @@ class IamResponse(BaseResponse):
template = self.response_template(LIST_USER_POLICIES_TEMPLATE)
return template.render(policies=policies)
def list_user_tags(self):
user_name = self._get_param("UserName")
tags = iam_backend.list_user_tags(user_name)
template = self.response_template(LIST_USER_TAGS_TEMPLATE)
return template.render(user_tags=tags or [])
def put_user_policy(self):
user_name = self._get_param("UserName")
policy_name = self._get_param("PolicyName")
@ -1699,6 +1705,23 @@ LIST_USER_POLICIES_TEMPLATE = """<ListUserPoliciesResponse>
</ResponseMetadata>
</ListUserPoliciesResponse>"""
LIST_USER_TAGS_TEMPLATE = """<ListUserTagsResponse>
<ListUserTagsResult>
<Tags>
{% for tag in user_tags %}
<item>
<Key>{{ tag.Key }}</Key>
<Value>{{ tag.Value }}</Value>
</item>
{% endfor %}
</Tags>
<IsTruncated>false</IsTruncated>
</ListUserTagsResult>
<ResponseMetadata>
<RequestId>7a62c49f-347e-4fc4-9331-6e8eEXAMPLE</RequestId>
</ResponseMetadata>
</ListUserTagsResponse>"""
CREATE_ACCESS_KEY_TEMPLATE = """<CreateAccessKeyResponse>
<CreateAccessKeyResult>
<AccessKey>

View File

@ -9,7 +9,7 @@ import uuid
from collections import OrderedDict
from datetime import datetime
import boto3
from boto3 import Session
from moto.core import BaseBackend, BaseModel
from .exceptions import (
@ -1160,5 +1160,10 @@ class IoTBackend(BaseBackend):
return job_executions, next_token
available_regions = boto3.session.Session().get_available_regions("iot")
iot_backends = {region: IoTBackend(region) for region in available_regions}
iot_backends = {}
for region in Session().get_available_regions("iot"):
iot_backends[region] = IoTBackend(region)
for region in Session().get_available_regions("iot", partition_name="aws-us-gov"):
iot_backends[region] = IoTBackend(region)
for region in Session().get_available_regions("iot", partition_name="aws-cn"):
iot_backends[region] = IoTBackend(region)

View File

@ -1,8 +1,9 @@
from __future__ import unicode_literals
import json
import time
import boto3
import jsondiff
from boto3 import Session
from moto.core import BaseBackend, BaseModel
from moto.iot import iot_backends
from .exceptions import (
@ -205,5 +206,10 @@ class IoTDataPlaneBackend(BaseBackend):
return None
available_regions = boto3.session.Session().get_available_regions("iot-data")
iotdata_backends = {region: IoTDataPlaneBackend(region) for region in available_regions}
iotdata_backends = {}
for region in Session().get_available_regions("iot-data"):
iotdata_backends[region] = IoTDataPlaneBackend(region)
for region in Session().get_available_regions("iot-data", partition_name="aws-us-gov"):
iotdata_backends[region] = IoTDataPlaneBackend(region)
for region in Session().get_available_regions("iot-data", partition_name="aws-cn"):
iotdata_backends[region] = IoTDataPlaneBackend(region)

View File

@ -2,7 +2,6 @@ from __future__ import unicode_literals
import datetime
import time
import boto.kinesis
import re
import six
import itertools
@ -10,6 +9,8 @@ import itertools
from operator import attrgetter
from hashlib import md5
from boto3 import Session
from moto.compat import OrderedDict
from moto.core import BaseBackend, BaseModel
from moto.core.utils import unix_time
@ -530,5 +531,9 @@ class KinesisBackend(BaseBackend):
kinesis_backends = {}
for region in boto.kinesis.regions():
kinesis_backends[region.name] = KinesisBackend()
for region in Session().get_available_regions("kinesis"):
kinesis_backends[region] = KinesisBackend()
for region in Session().get_available_regions("kinesis", partition_name="aws-us-gov"):
kinesis_backends[region] = KinesisBackend()
for region in Session().get_available_regions("kinesis", partition_name="aws-cn"):
kinesis_backends[region] = KinesisBackend()

View File

@ -4,7 +4,7 @@ import os
from collections import defaultdict
from datetime import datetime, timedelta
import boto.kms
from boto3 import Session
from moto.core import BaseBackend, BaseModel
from moto.core.utils import iso_8601_datetime_without_milliseconds
@ -284,5 +284,9 @@ class KmsBackend(BaseBackend):
kms_backends = {}
for region in boto.kms.regions():
kms_backends[region.name] = KmsBackend()
for region in Session().get_available_regions("kms"):
kms_backends[region] = KmsBackend()
for region in Session().get_available_regions("kms", partition_name="aws-us-gov"):
kms_backends[region] = KmsBackend()
for region in Session().get_available_regions("kms", partition_name="aws-cn"):
kms_backends[region] = KmsBackend()

View File

@ -1,5 +1,6 @@
from boto3 import Session
from moto.core import BaseBackend
import boto.logs
from moto.core.utils import unix_time_millis
from .exceptions import (
ResourceNotFoundException,
@ -558,6 +559,10 @@ class LogsBackend(BaseBackend):
log_group.untag(tags)
logs_backends = {
region.name: LogsBackend(region.name) for region in boto.logs.regions()
}
logs_backends = {}
for region in Session().get_available_regions("logs"):
logs_backends[region] = LogsBackend(region)
for region in Session().get_available_regions("logs", partition_name="aws-us-gov"):
logs_backends[region] = LogsBackend(region)
for region in Session().get_available_regions("logs", partition_name="aws-cn"):
logs_backends[region] = LogsBackend(region)

View File

@ -2,7 +2,8 @@ from __future__ import unicode_literals
from xml.etree import ElementTree as ET
import datetime
import boto3
from boto3 import Session
from moto.core import BaseBackend, BaseModel
from .resources import VOICE_DATA
@ -113,7 +114,10 @@ class PollyBackend(BaseBackend):
self._lexicons[name] = lexicon
available_regions = boto3.session.Session().get_available_regions("polly")
polly_backends = {
region: PollyBackend(region_name=region) for region in available_regions
}
polly_backends = {}
for region in Session().get_available_regions("polly"):
polly_backends[region] = PollyBackend(region)
for region in Session().get_available_regions("polly", partition_name="aws-us-gov"):
polly_backends[region] = PollyBackend(region)
for region in Session().get_available_regions("polly", partition_name="aws-cn"):
polly_backends[region] = PollyBackend(region)

View File

@ -5,7 +5,7 @@ import datetime
import os
from collections import defaultdict
import boto.rds2
from boto3 import Session
from jinja2 import Template
from re import compile as re_compile
from moto.cloudformation.exceptions import UnformattedGetAttTemplateException
@ -1501,6 +1501,10 @@ class DBParameterGroup(object):
return db_parameter_group
rds2_backends = dict(
(region.name, RDS2Backend(region.name)) for region in boto.rds2.regions()
)
rds2_backends = {}
for region in Session().get_available_regions("rds"):
rds2_backends[region] = RDS2Backend(region)
for region in Session().get_available_regions("rds", partition_name="aws-us-gov"):
rds2_backends[region] = RDS2Backend(region)
for region in Session().get_available_regions("rds", partition_name="aws-cn"):
rds2_backends[region] = RDS2Backend(region)

View File

@ -3,7 +3,7 @@ from __future__ import unicode_literals
import copy
import datetime
import boto.redshift
from boto3 import Session
from botocore.exceptions import ClientError
from moto.compat import OrderedDict
from moto.core import BaseBackend, BaseModel
@ -897,7 +897,9 @@ class RedshiftBackend(BaseBackend):
redshift_backends = {}
for region in boto.redshift.regions():
redshift_backends[region.name] = RedshiftBackend(
ec2_backends[region.name], region.name
)
for region in Session().get_available_regions("redshift"):
redshift_backends[region] = RedshiftBackend(ec2_backends[region], region)
for region in Session().get_available_regions("redshift", partition_name="aws-us-gov"):
redshift_backends[region] = RedshiftBackend(ec2_backends[region], region)
for region in Session().get_available_regions("redshift", partition_name="aws-cn"):
redshift_backends[region] = RedshiftBackend(ec2_backends[region], region)

View File

@ -1,10 +1,11 @@
from __future__ import unicode_literals
from builtins import str
import boto3
import json
import re
from boto3 import Session
from moto.core import BaseBackend, BaseModel
from moto.core import ACCOUNT_ID
from .exceptions import BadRequestException
@ -350,7 +351,14 @@ class ResourceGroupsBackend(BaseBackend):
return self.groups.by_name[group_name]
available_regions = boto3.session.Session().get_available_regions("resource-groups")
resourcegroups_backends = {
region: ResourceGroupsBackend(region_name=region) for region in available_regions
}
resourcegroups_backends = {}
for region in Session().get_available_regions("resource-groups"):
resourcegroups_backends[region] = ResourceGroupsBackend(region)
for region in Session().get_available_regions(
"resource-groups", partition_name="aws-us-gov"
):
resourcegroups_backends[region] = ResourceGroupsBackend(region)
for region in Session().get_available_regions(
"resource-groups", partition_name="aws-cn"
):
resourcegroups_backends[region] = ResourceGroupsBackend(region)

View File

@ -1,7 +1,8 @@
from __future__ import unicode_literals
import uuid
import boto3
import six
from boto3 import Session
from moto.core import BaseBackend
from moto.core.exceptions import RESTError
@ -636,9 +637,14 @@ class ResourceGroupsTaggingAPIBackend(BaseBackend):
# return failed_resources_map
available_regions = boto3.session.Session().get_available_regions(
"resourcegroupstaggingapi"
)
resourcegroupstaggingapi_backends = {
region: ResourceGroupsTaggingAPIBackend(region) for region in available_regions
}
resourcegroupstaggingapi_backends = {}
for region in Session().get_available_regions("resourcegroupstaggingapi"):
resourcegroupstaggingapi_backends[region] = ResourceGroupsTaggingAPIBackend(region)
for region in Session().get_available_regions(
"resourcegroupstaggingapi", partition_name="aws-us-gov"
):
resourcegroupstaggingapi_backends[region] = ResourceGroupsTaggingAPIBackend(region)
for region in Session().get_available_regions(
"resourcegroupstaggingapi", partition_name="aws-cn"
):
resourcegroupstaggingapi_backends[region] = ResourceGroupsTaggingAPIBackend(region)

View File

@ -271,6 +271,7 @@ LIST_RRSET_RESPONSE = """<ListResourceRecordSetsResponse xmlns="https://route53.
{{ record_set.to_xml() }}
{% endfor %}
</ResourceRecordSets>
<IsTruncated>false</IsTruncated>
</ListResourceRecordSetsResponse>"""
CHANGE_RRSET_RESPONSE = """<ChangeResourceRecordSetsResponse xmlns="https://route53.amazonaws.com/doc/2012-12-12/">

View File

@ -1482,7 +1482,7 @@ S3_ALL_BUCKETS = """<ListAllMyBucketsResult xmlns="http://s3.amazonaws.com/doc/2
{% for bucket in buckets %}
<Bucket>
<Name>{{ bucket.name }}</Name>
<CreationDate>{{ bucket.creation_date }}</CreationDate>
<CreationDate>{{ bucket.creation_date.isoformat() }}</CreationDate>
</Bucket>
{% endfor %}
</Buckets>
@ -1491,7 +1491,9 @@ S3_ALL_BUCKETS = """<ListAllMyBucketsResult xmlns="http://s3.amazonaws.com/doc/2
S3_BUCKET_GET_RESPONSE = """<?xml version="1.0" encoding="UTF-8"?>
<ListBucketResult xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
<Name>{{ bucket.name }}</Name>
{% if prefix != None %}
<Prefix>{{ prefix }}</Prefix>
{% endif %}
<MaxKeys>{{ max_keys }}</MaxKeys>
<Delimiter>{{ delimiter }}</Delimiter>
<IsTruncated>{{ is_truncated }}</IsTruncated>
@ -1523,7 +1525,9 @@ S3_BUCKET_GET_RESPONSE = """<?xml version="1.0" encoding="UTF-8"?>
S3_BUCKET_GET_RESPONSE_V2 = """<?xml version="1.0" encoding="UTF-8"?>
<ListBucketResult xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
<Name>{{ bucket.name }}</Name>
{% if prefix != None %}
<Prefix>{{ prefix }}</Prefix>
{% endif %}
<MaxKeys>{{ max_keys }}</MaxKeys>
<KeyCount>{{ key_count }}</KeyCount>
{% if delimiter %}
@ -1684,7 +1688,9 @@ S3_BUCKET_GET_VERSIONING = """<?xml version="1.0" encoding="UTF-8"?>
S3_BUCKET_GET_VERSIONS = """<?xml version="1.0" encoding="UTF-8"?>
<ListVersionsResult xmlns="http://s3.amazonaws.com/doc/2006-03-01">
<Name>{{ bucket.name }}</Name>
{% if prefix != None %}
<Prefix>{{ prefix }}</Prefix>
{% endif %}
<KeyMarker>{{ key_marker }}</KeyMarker>
<MaxKeys>{{ max_keys }}</MaxKeys>
<IsTruncated>{{ is_truncated }}</IsTruncated>
@ -1863,7 +1869,6 @@ S3_MULTIPART_LIST_RESPONSE = """<?xml version="1.0" encoding="UTF-8"?>
<ID>75aa57f09aa0c8caeab4f8c24e99d10f8e7faeebf76c078efc7c6caea54ba06a</ID>
<DisplayName>webfile</DisplayName>
</Owner>
<StorageClass>STANDARD</StorageClass>
<PartNumberMarker>1</PartNumberMarker>
<NextPartNumberMarker>{{ count }}</NextPartNumberMarker>
<MaxParts>{{ count }}</MaxParts>

View File

@ -6,7 +6,7 @@ import json
import uuid
import datetime
import boto3
from boto3 import Session
from moto.core import BaseBackend, BaseModel
from .exceptions import (
@ -491,7 +491,14 @@ class SecretsManagerBackend(BaseBackend):
)
available_regions = boto3.session.Session().get_available_regions("secretsmanager")
secretsmanager_backends = {
region: SecretsManagerBackend(region_name=region) for region in available_regions
}
secretsmanager_backends = {}
for region in Session().get_available_regions("secretsmanager"):
secretsmanager_backends[region] = SecretsManagerBackend(region_name=region)
for region in Session().get_available_regions(
"secretsmanager", partition_name="aws-us-gov"
):
secretsmanager_backends[region] = SecretsManagerBackend(region_name=region)
for region in Session().get_available_regions(
"secretsmanager", partition_name="aws-cn"
):
secretsmanager_backends[region] = SecretsManagerBackend(region_name=region)

View File

@ -397,10 +397,6 @@ class SNSBackend(BaseBackend):
return self._get_values_nexttoken(self.topics, next_token)
def delete_topic(self, arn):
topic = self.get_topic(arn)
subscriptions = self._get_topic_subscriptions(topic)
for sub in subscriptions:
self.unsubscribe(sub.arn)
self.topics.pop(arn)
def get_topic(self, arn):
@ -466,7 +462,7 @@ class SNSBackend(BaseBackend):
return None
def unsubscribe(self, subscription_arn):
self.subscriptions.pop(subscription_arn)
self.subscriptions.pop(subscription_arn, None)
def list_subscriptions(self, topic_arn=None, next_token=None):
if topic_arn:
@ -707,6 +703,10 @@ class SNSBackend(BaseBackend):
sns_backends = {}
for region in Session().get_available_regions("sns"):
sns_backends[region] = SNSBackend(region)
for region in Session().get_available_regions("sns", partition_name="aws-us-gov"):
sns_backends[region] = SNSBackend(region)
for region in Session().get_available_regions("sns", partition_name="aws-cn"):
sns_backends[region] = SNSBackend(region)
DEFAULT_EFFECTIVE_DELIVERY_POLICY = {

View File

@ -8,7 +8,7 @@ import six
import struct
from xml.sax.saxutils import escape
import boto.sqs
from boto3 import Session
from moto.core.exceptions import RESTError
from moto.core import BaseBackend, BaseModel
@ -183,6 +183,7 @@ class Queue(BaseModel):
"MaximumMessageSize",
"MessageRetentionPeriod",
"QueueArn",
"RedrivePolicy",
"ReceiveMessageWaitTimeSeconds",
"VisibilityTimeout",
]
@ -857,5 +858,9 @@ class SQSBackend(BaseBackend):
sqs_backends = {}
for region in boto.sqs.regions():
sqs_backends[region.name] = SQSBackend(region.name)
for region in Session().get_available_regions("sqs"):
sqs_backends[region] = SQSBackend(region)
for region in Session().get_available_regions("sqs", partition_name="aws-us-gov"):
sqs_backends[region] = SQSBackend(region)
for region in Session().get_available_regions("sqs", partition_name="aws-cn"):
sqs_backends[region] = SQSBackend(region)

View File

@ -1,6 +1,8 @@
import boto
import re
from datetime import datetime
from boto3 import Session
from moto.core import BaseBackend
from moto.core.utils import iso_8601_datetime_without_milliseconds
from moto.sts.models import ACCOUNT_ID
@ -280,7 +282,12 @@ class StepFunctionBackend(BaseBackend):
return ACCOUNT_ID
stepfunction_backends = {
_region.name: StepFunctionBackend(_region.name)
for _region in boto.awslambda.regions()
}
stepfunction_backends = {}
for region in Session().get_available_regions("stepfunctions"):
stepfunction_backends[region] = StepFunctionBackend(region)
for region in Session().get_available_regions(
"stepfunctions", partition_name="aws-us-gov"
):
stepfunction_backends[region] = StepFunctionBackend(region)
for region in Session().get_available_regions("stepfunctions", partition_name="aws-cn"):
stepfunction_backends[region] = StepFunctionBackend(region)

View File

@ -1,6 +1,6 @@
from __future__ import unicode_literals
import boto.swf
from boto3 import Session
from moto.core import BaseBackend
@ -418,5 +418,9 @@ class SWFBackend(BaseBackend):
swf_backends = {}
for region in boto.swf.regions():
swf_backends[region.name] = SWFBackend(region.name)
for region in Session().get_available_regions("swf"):
swf_backends[region] = SWFBackend(region)
for region in Session().get_available_regions("swf", partition_name="aws-us-gov"):
swf_backends[region] = SWFBackend(region)
for region in Session().get_available_regions("swf", partition_name="aws-cn"):
swf_backends[region] = SWFBackend(region)

View File

@ -20,8 +20,8 @@ import jinja2
from prompt_toolkit import (
prompt
)
from prompt_toolkit.contrib.completers import WordCompleter
from prompt_toolkit.shortcuts import print_tokens
from prompt_toolkit.completion import WordCompleter
from prompt_toolkit.shortcuts import print_formatted_text
from botocore import xform_name
from botocore.session import Session
@ -149,12 +149,12 @@ def append_mock_dict_to_backends_py(service):
with open(path) as f:
lines = [_.replace('\n', '') for _ in f.readlines()]
if any(_ for _ in lines if re.match(".*'{}': {}_backends.*".format(service, service), _)):
if any(_ for _ in lines if re.match(".*\"{}\": {}_backends.*".format(service, service), _)):
return
filtered_lines = [_ for _ in lines if re.match(".*'.*':.*_backends.*", _)]
filtered_lines = [_ for _ in lines if re.match(".*\".*\":.*_backends.*", _)]
last_elem_line_index = lines.index(filtered_lines[-1])
new_line = " '{}': {}_backends,".format(service, get_escaped_service(service))
new_line = " \"{}\": {}_backends,".format(service, get_escaped_service(service))
prev_line = lines[last_elem_line_index]
if not prev_line.endswith('{') and not prev_line.endswith(','):
lines[last_elem_line_index] += ','

View File

@ -1,5 +1,5 @@
from __future__ import unicode_literals
import boto3
from boto3 import Session
from moto.core import BaseBackend, BaseModel
@ -16,5 +16,10 @@ class {{ service_class }}Backend(BaseBackend):
# add methods from here
available_regions = boto3.session.Session().get_available_regions("{{ service }}")
{{ escaped_service }}_backends = {region: {{ service_class }}Backend(region) for region in available_regions}
{{ escaped_service }}_backends = {}
for region in Session().get_available_regions("{{ service }}"):
{{ escaped_service }}_backends[region] = {{ service_class }}Backend()
for region in Session().get_available_regions("{{ service }}", partition_name="aws-us-gov"):
{{ escaped_service }}_backends[region] = {{ service_class }}Backend()
for region in Session().get_available_regions("{{ service }}", partition_name="aws-cn"):
{{ escaped_service }}_backends[region] = {{ service_class }}Backend()

View File

@ -43,7 +43,7 @@ install_requires = [
"python-jose<4.0.0",
"mock",
"docker>=2.5.1",
"jsondiff==1.1.2",
"jsondiff>=1.1.2",
"aws-xray-sdk!=0.96,>=0.93",
"responses>=0.9.0",
"idna<2.9,>=2.5",

View File

@ -706,14 +706,14 @@ def test_create_autoscaling_group_boto3():
"ResourceId": "test_asg",
"ResourceType": "auto-scaling-group",
"Key": "propogated-tag-key",
"Value": "propogate-tag-value",
"Value": "propagate-tag-value",
"PropagateAtLaunch": True,
},
{
"ResourceId": "test_asg",
"ResourceType": "auto-scaling-group",
"Key": "not-propogated-tag-key",
"Value": "not-propogate-tag-value",
"Value": "not-propagate-tag-value",
"PropagateAtLaunch": False,
},
],
@ -744,14 +744,14 @@ def test_create_autoscaling_group_from_instance():
"ResourceId": "test_asg",
"ResourceType": "auto-scaling-group",
"Key": "propogated-tag-key",
"Value": "propogate-tag-value",
"Value": "propagate-tag-value",
"PropagateAtLaunch": True,
},
{
"ResourceId": "test_asg",
"ResourceType": "auto-scaling-group",
"Key": "not-propogated-tag-key",
"Value": "not-propogate-tag-value",
"Value": "not-propagate-tag-value",
"PropagateAtLaunch": False,
},
],
@ -1062,7 +1062,7 @@ def test_detach_one_instance_decrement():
"ResourceId": "test_asg",
"ResourceType": "auto-scaling-group",
"Key": "propogated-tag-key",
"Value": "propogate-tag-value",
"Value": "propagate-tag-value",
"PropagateAtLaunch": True,
}
],
@ -1116,7 +1116,7 @@ def test_detach_one_instance():
"ResourceId": "test_asg",
"ResourceType": "auto-scaling-group",
"Key": "propogated-tag-key",
"Value": "propogate-tag-value",
"Value": "propagate-tag-value",
"PropagateAtLaunch": True,
}
],
@ -1169,7 +1169,7 @@ def test_attach_one_instance():
"ResourceId": "test_asg",
"ResourceType": "auto-scaling-group",
"Key": "propogated-tag-key",
"Value": "propogate-tag-value",
"Value": "propagate-tag-value",
"PropagateAtLaunch": True,
}
],

View File

@ -58,8 +58,7 @@ def lambda_handler(event, context):
volume_id = event.get('volume_id')
vol = ec2.Volume(volume_id)
print('get volume details for %s\\nVolume - %s state=%s, size=%s' % (volume_id, volume_id, vol.state, vol.size))
return event
return {{'id': vol.id, 'state': vol.state, 'size': vol.size}}
""".format(
base_url="motoserver:5000"
if settings.TEST_SERVER_MODE
@ -181,27 +180,9 @@ if settings.TEST_SERVER_MODE:
Payload=json.dumps(in_data),
)
result["StatusCode"].should.equal(202)
msg = "get volume details for %s\nVolume - %s state=%s, size=%s\n%s" % (
vol.id,
vol.id,
vol.state,
vol.size,
json.dumps(in_data).replace(
" ", ""
), # Makes the tests pass as the result is missing the whitespace
)
log_result = base64.b64decode(result["LogResult"]).decode("utf-8")
# The Docker lambda invocation will return an additional '\n', so need to replace it:
log_result = log_result.replace("\n\n", "\n")
log_result.should.equal(msg)
payload = result["Payload"].read().decode("utf-8")
# The Docker lambda invocation will return an additional '\n', so need to replace it:
payload = payload.replace("\n\n", "\n")
payload.should.equal(msg)
actual_payload = json.loads(result["Payload"].read().decode("utf-8"))
expected_payload = {"id": vol.id, "state": vol.state, "size": vol.size}
actual_payload.should.equal(expected_payload)
@mock_logs

View File

@ -0,0 +1,222 @@
import boto3
import sure # noqa
from moto import mock_codecommit
from moto.iam.models import ACCOUNT_ID
from botocore.exceptions import ClientError
from nose.tools import assert_raises
@mock_codecommit
def test_create_repository():
client = boto3.client("codecommit", region_name="eu-central-1")
response = client.create_repository(
repositoryName="repository_one", repositoryDescription="description repo one"
)
response.should_not.be.none
response["repositoryMetadata"].should_not.be.none
response["repositoryMetadata"]["creationDate"].should_not.be.none
response["repositoryMetadata"]["lastModifiedDate"].should_not.be.none
response["repositoryMetadata"]["repositoryId"].should_not.be.empty
response["repositoryMetadata"]["repositoryName"].should.equal("repository_one")
response["repositoryMetadata"]["repositoryDescription"].should.equal(
"description repo one"
)
response["repositoryMetadata"]["cloneUrlSsh"].should.equal(
"ssh://git-codecommit.{0}.amazonaws.com/v1/repos/{1}".format(
"eu-central-1", "repository_one"
)
)
response["repositoryMetadata"]["cloneUrlHttp"].should.equal(
"https://git-codecommit.{0}.amazonaws.com/v1/repos/{1}".format(
"eu-central-1", "repository_one"
)
)
response["repositoryMetadata"]["Arn"].should.equal(
"arn:aws:codecommit:{0}:{1}:{2}".format(
"eu-central-1", ACCOUNT_ID, "repository_one"
)
)
response["repositoryMetadata"]["accountId"].should.equal(ACCOUNT_ID)
@mock_codecommit
def test_create_repository_without_description():
client = boto3.client("codecommit", region_name="eu-central-1")
response = client.create_repository(repositoryName="repository_two")
response.should_not.be.none
response.get("repositoryMetadata").should_not.be.none
response.get("repositoryMetadata").get("repositoryName").should.equal(
"repository_two"
)
response.get("repositoryMetadata").get("repositoryDescription").should.be.none
response["repositoryMetadata"].should_not.be.none
response["repositoryMetadata"]["creationDate"].should_not.be.none
response["repositoryMetadata"]["lastModifiedDate"].should_not.be.none
response["repositoryMetadata"]["repositoryId"].should_not.be.empty
response["repositoryMetadata"]["cloneUrlSsh"].should.equal(
"ssh://git-codecommit.{0}.amazonaws.com/v1/repos/{1}".format(
"eu-central-1", "repository_two"
)
)
response["repositoryMetadata"]["cloneUrlHttp"].should.equal(
"https://git-codecommit.{0}.amazonaws.com/v1/repos/{1}".format(
"eu-central-1", "repository_two"
)
)
response["repositoryMetadata"]["Arn"].should.equal(
"arn:aws:codecommit:{0}:{1}:{2}".format(
"eu-central-1", ACCOUNT_ID, "repository_two"
)
)
response["repositoryMetadata"]["accountId"].should.equal(ACCOUNT_ID)
@mock_codecommit
def test_create_repository_repository_name_exists():
client = boto3.client("codecommit", region_name="eu-central-1")
client.create_repository(repositoryName="repository_two")
with assert_raises(ClientError) as e:
client.create_repository(
repositoryName="repository_two",
repositoryDescription="description repo two",
)
ex = e.exception
ex.operation_name.should.equal("CreateRepository")
ex.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
ex.response["Error"]["Code"].should.contain("RepositoryNameExistsException")
ex.response["Error"]["Message"].should.equal(
"Repository named {0} already exists".format("repository_two")
)
@mock_codecommit
def test_create_repository_invalid_repository_name():
client = boto3.client("codecommit", region_name="eu-central-1")
with assert_raises(ClientError) as e:
client.create_repository(repositoryName="in_123_valid_@#$_characters")
ex = e.exception
ex.operation_name.should.equal("CreateRepository")
ex.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
ex.response["Error"]["Code"].should.contain("InvalidRepositoryNameException")
ex.response["Error"]["Message"].should.equal(
"The repository name is not valid. Repository names can be any valid "
"combination of letters, numbers, "
"periods, underscores, and dashes between 1 and 100 characters in "
"length. Names are case sensitive. "
"For more information, see Limits in the AWS CodeCommit User Guide. "
)
@mock_codecommit
def test_get_repository():
client = boto3.client("codecommit", region_name="eu-central-1")
repository_name = "repository_one"
client.create_repository(
repositoryName=repository_name, repositoryDescription="description repo one"
)
response = client.get_repository(repositoryName=repository_name)
response.should_not.be.none
response.get("repositoryMetadata").should_not.be.none
response.get("repositoryMetadata").get("creationDate").should_not.be.none
response.get("repositoryMetadata").get("lastModifiedDate").should_not.be.none
response.get("repositoryMetadata").get("repositoryId").should_not.be.empty
response.get("repositoryMetadata").get("repositoryName").should.equal(
repository_name
)
response.get("repositoryMetadata").get("repositoryDescription").should.equal(
"description repo one"
)
response.get("repositoryMetadata").get("cloneUrlSsh").should.equal(
"ssh://git-codecommit.{0}.amazonaws.com/v1/repos/{1}".format(
"eu-central-1", "repository_one"
)
)
response.get("repositoryMetadata").get("cloneUrlHttp").should.equal(
"https://git-codecommit.{0}.amazonaws.com/v1/repos/{1}".format(
"eu-central-1", "repository_one"
)
)
response.get("repositoryMetadata").get("Arn").should.equal(
"arn:aws:codecommit:{0}:{1}:{2}".format(
"eu-central-1", ACCOUNT_ID, "repository_one"
)
)
response.get("repositoryMetadata").get("accountId").should.equal(ACCOUNT_ID)
client = boto3.client("codecommit", region_name="us-east-1")
with assert_raises(ClientError) as e:
client.get_repository(repositoryName=repository_name)
ex = e.exception
ex.operation_name.should.equal("GetRepository")
ex.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
ex.response["Error"]["Code"].should.contain("RepositoryDoesNotExistException")
ex.response["Error"]["Message"].should.equal(
"{0} does not exist".format(repository_name)
)
@mock_codecommit
def test_get_repository_invalid_repository_name():
client = boto3.client("codecommit", region_name="eu-central-1")
with assert_raises(ClientError) as e:
client.get_repository(repositoryName="repository_one-@#@")
ex = e.exception
ex.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
ex.response["Error"]["Code"].should.contain("InvalidRepositoryNameException")
ex.response["Error"]["Message"].should.equal(
"The repository name is not valid. Repository names can be any valid "
"combination of letters, numbers, "
"periods, underscores, and dashes between 1 and 100 characters in "
"length. Names are case sensitive. "
"For more information, see Limits in the AWS CodeCommit User Guide. "
)
@mock_codecommit
def test_delete_repository():
client = boto3.client("codecommit", region_name="us-east-1")
response = client.create_repository(repositoryName="repository_one")
repository_id_create = response.get("repositoryMetadata").get("repositoryId")
response = client.delete_repository(repositoryName="repository_one")
response.get("repositoryId").should_not.be.none
repository_id_create.should.equal(response.get("repositoryId"))
response = client.delete_repository(repositoryName="unknown_repository")
response.get("repositoryId").should.be.none
@mock_codecommit
def test_delete_repository_invalid_repository_name():
client = boto3.client("codecommit", region_name="us-east-1")
with assert_raises(ClientError) as e:
client.delete_repository(repositoryName="_rep@ository_one")
ex = e.exception
ex.operation_name.should.equal("DeleteRepository")
ex.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
ex.response["Error"]["Code"].should.contain("InvalidRepositoryNameException")
ex.response["Error"]["Message"].should.equal(
"The repository name is not valid. Repository names can be any valid "
"combination of letters, numbers, "
"periods, underscores, and dashes between 1 and 100 characters in "
"length. Names are case sensitive. "
"For more information, see Limits in the AWS CodeCommit User Guide. "
)

View File

@ -13,52 +13,7 @@ from moto import mock_codepipeline, mock_iam
def test_create_pipeline():
client = boto3.client("codepipeline", region_name="us-east-1")
response = client.create_pipeline(
pipeline={
"name": "test-pipeline",
"roleArn": get_role_arn(),
"artifactStore": {
"type": "S3",
"location": "codepipeline-us-east-1-123456789012",
},
"stages": [
{
"name": "Stage-1",
"actions": [
{
"name": "Action-1",
"actionTypeId": {
"category": "Source",
"owner": "AWS",
"provider": "S3",
"version": "1",
},
"configuration": {
"S3Bucket": "test-bucket",
"S3ObjectKey": "test-object",
},
"outputArtifacts": [{"name": "artifact"},],
},
],
},
{
"name": "Stage-2",
"actions": [
{
"name": "Action-1",
"actionTypeId": {
"category": "Approval",
"owner": "AWS",
"provider": "Manual",
"version": "1",
},
},
],
},
],
},
tags=[{"key": "key", "value": "value"}],
)
response = create_basic_codepipeline(client, "test-pipeline")
response["pipeline"].should.equal(
{
@ -120,98 +75,10 @@ def test_create_pipeline():
def test_create_pipeline_errors():
client = boto3.client("codepipeline", region_name="us-east-1")
client_iam = boto3.client("iam", region_name="us-east-1")
client.create_pipeline(
pipeline={
"name": "test-pipeline",
"roleArn": get_role_arn(),
"artifactStore": {
"type": "S3",
"location": "codepipeline-us-east-1-123456789012",
},
"stages": [
{
"name": "Stage-1",
"actions": [
{
"name": "Action-1",
"actionTypeId": {
"category": "Source",
"owner": "AWS",
"provider": "S3",
"version": "1",
},
"configuration": {
"S3Bucket": "test-bucket",
"S3ObjectKey": "test-object",
},
"outputArtifacts": [{"name": "artifact"},],
},
],
},
{
"name": "Stage-2",
"actions": [
{
"name": "Action-1",
"actionTypeId": {
"category": "Approval",
"owner": "AWS",
"provider": "Manual",
"version": "1",
},
},
],
},
],
}
)
create_basic_codepipeline(client, "test-pipeline")
with assert_raises(ClientError) as e:
client.create_pipeline(
pipeline={
"name": "test-pipeline",
"roleArn": get_role_arn(),
"artifactStore": {
"type": "S3",
"location": "codepipeline-us-east-1-123456789012",
},
"stages": [
{
"name": "Stage-1",
"actions": [
{
"name": "Action-1",
"actionTypeId": {
"category": "Source",
"owner": "AWS",
"provider": "S3",
"version": "1",
},
"configuration": {
"S3Bucket": "test-bucket",
"S3ObjectKey": "test-object",
},
"outputArtifacts": [{"name": "artifact"},],
},
],
},
{
"name": "Stage-2",
"actions": [
{
"name": "Action-1",
"actionTypeId": {
"category": "Approval",
"owner": "AWS",
"provider": "Manual",
"version": "1",
},
},
],
},
],
}
)
create_basic_codepipeline(client, "test-pipeline")
ex = e.exception
ex.operation_name.should.equal("CreatePipeline")
ex.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
@ -348,52 +215,7 @@ def test_create_pipeline_errors():
@mock_codepipeline
def test_get_pipeline():
client = boto3.client("codepipeline", region_name="us-east-1")
client.create_pipeline(
pipeline={
"name": "test-pipeline",
"roleArn": get_role_arn(),
"artifactStore": {
"type": "S3",
"location": "codepipeline-us-east-1-123456789012",
},
"stages": [
{
"name": "Stage-1",
"actions": [
{
"name": "Action-1",
"actionTypeId": {
"category": "Source",
"owner": "AWS",
"provider": "S3",
"version": "1",
},
"configuration": {
"S3Bucket": "test-bucket",
"S3ObjectKey": "test-object",
},
"outputArtifacts": [{"name": "artifact"},],
},
],
},
{
"name": "Stage-2",
"actions": [
{
"name": "Action-1",
"actionTypeId": {
"category": "Approval",
"owner": "AWS",
"provider": "Manual",
"version": "1",
},
},
],
},
],
},
tags=[{"key": "key", "value": "value"}],
)
create_basic_codepipeline(client, "test-pipeline")
response = client.get_pipeline(name="test-pipeline")
@ -474,53 +296,7 @@ def test_get_pipeline_errors():
@mock_codepipeline
def test_update_pipeline():
client = boto3.client("codepipeline", region_name="us-east-1")
role_arn = get_role_arn()
client.create_pipeline(
pipeline={
"name": "test-pipeline",
"roleArn": role_arn,
"artifactStore": {
"type": "S3",
"location": "codepipeline-us-east-1-123456789012",
},
"stages": [
{
"name": "Stage-1",
"actions": [
{
"name": "Action-1",
"actionTypeId": {
"category": "Source",
"owner": "AWS",
"provider": "S3",
"version": "1",
},
"configuration": {
"S3Bucket": "test-bucket",
"S3ObjectKey": "test-object",
},
"outputArtifacts": [{"name": "artifact"},],
},
],
},
{
"name": "Stage-2",
"actions": [
{
"name": "Action-1",
"actionTypeId": {
"category": "Approval",
"owner": "AWS",
"provider": "Manual",
"version": "1",
},
},
],
},
],
},
tags=[{"key": "key", "value": "value"}],
)
create_basic_codepipeline(client, "test-pipeline")
response = client.get_pipeline(name="test-pipeline")
created_time = response["metadata"]["created"]
@ -529,7 +305,7 @@ def test_update_pipeline():
response = client.update_pipeline(
pipeline={
"name": "test-pipeline",
"roleArn": role_arn,
"roleArn": get_role_arn(),
"artifactStore": {
"type": "S3",
"location": "codepipeline-us-east-1-123456789012",
@ -692,105 +468,19 @@ def test_update_pipeline_errors():
@mock_codepipeline
def test_list_pipelines():
client = boto3.client("codepipeline", region_name="us-east-1")
client.create_pipeline(
pipeline={
"name": "test-pipeline-1",
"roleArn": get_role_arn(),
"artifactStore": {
"type": "S3",
"location": "codepipeline-us-east-1-123456789012",
},
"stages": [
{
"name": "Stage-1",
"actions": [
{
"name": "Action-1",
"actionTypeId": {
"category": "Source",
"owner": "AWS",
"provider": "S3",
"version": "1",
},
"configuration": {
"S3Bucket": "test-bucket",
"S3ObjectKey": "test-object",
},
"outputArtifacts": [{"name": "artifact"},],
},
],
},
{
"name": "Stage-2",
"actions": [
{
"name": "Action-1",
"actionTypeId": {
"category": "Approval",
"owner": "AWS",
"provider": "Manual",
"version": "1",
},
},
],
},
],
},
)
client.create_pipeline(
pipeline={
"name": "test-pipeline-2",
"roleArn": get_role_arn(),
"artifactStore": {
"type": "S3",
"location": "codepipeline-us-east-1-123456789012",
},
"stages": [
{
"name": "Stage-1",
"actions": [
{
"name": "Action-1",
"actionTypeId": {
"category": "Source",
"owner": "AWS",
"provider": "S3",
"version": "1",
},
"configuration": {
"S3Bucket": "test-bucket",
"S3ObjectKey": "test-object",
},
"outputArtifacts": [{"name": "artifact"},],
},
],
},
{
"name": "Stage-2",
"actions": [
{
"name": "Action-1",
"actionTypeId": {
"category": "Approval",
"owner": "AWS",
"provider": "Manual",
"version": "1",
},
},
],
},
],
},
)
name_1 = "test-pipeline-1"
create_basic_codepipeline(client, name_1)
name_2 = "test-pipeline-2"
create_basic_codepipeline(client, name_2)
response = client.list_pipelines()
response["pipelines"].should.have.length_of(2)
response["pipelines"][0]["name"].should.equal("test-pipeline-1")
response["pipelines"][0]["name"].should.equal(name_1)
response["pipelines"][0]["version"].should.equal(1)
response["pipelines"][0]["created"].should.be.a(datetime)
response["pipelines"][0]["updated"].should.be.a(datetime)
response["pipelines"][1]["name"].should.equal("test-pipeline-2")
response["pipelines"][1]["name"].should.equal(name_2)
response["pipelines"][1]["version"].should.equal(1)
response["pipelines"][1]["created"].should.be.a(datetime)
response["pipelines"][1]["updated"].should.be.a(datetime)
@ -799,68 +489,172 @@ def test_list_pipelines():
@mock_codepipeline
def test_delete_pipeline():
client = boto3.client("codepipeline", region_name="us-east-1")
client.create_pipeline(
pipeline={
"name": "test-pipeline",
"roleArn": get_role_arn(),
"artifactStore": {
"type": "S3",
"location": "codepipeline-us-east-1-123456789012",
},
"stages": [
{
"name": "Stage-1",
"actions": [
{
"name": "Action-1",
"actionTypeId": {
"category": "Source",
"owner": "AWS",
"provider": "S3",
"version": "1",
},
"configuration": {
"S3Bucket": "test-bucket",
"S3ObjectKey": "test-object",
},
"outputArtifacts": [{"name": "artifact"},],
},
],
},
{
"name": "Stage-2",
"actions": [
{
"name": "Action-1",
"actionTypeId": {
"category": "Approval",
"owner": "AWS",
"provider": "Manual",
"version": "1",
},
},
],
},
],
},
)
name = "test-pipeline"
create_basic_codepipeline(client, name)
client.list_pipelines()["pipelines"].should.have.length_of(1)
client.delete_pipeline(name="test-pipeline")
client.delete_pipeline(name=name)
client.list_pipelines()["pipelines"].should.have.length_of(0)
# deleting a not existing pipeline, should raise no exception
client.delete_pipeline(name="test-pipeline")
client.delete_pipeline(name=name)
@mock_codepipeline
def test_list_tags_for_resource():
client = boto3.client("codepipeline", region_name="us-east-1")
name = "test-pipeline"
create_basic_codepipeline(client, name)
response = client.list_tags_for_resource(
resourceArn="arn:aws:codepipeline:us-east-1:123456789012:{}".format(name)
)
response["tags"].should.equal([{"key": "key", "value": "value"}])
@mock_codepipeline
def test_list_tags_for_resource_errors():
client = boto3.client("codepipeline", region_name="us-east-1")
with assert_raises(ClientError) as e:
client.list_tags_for_resource(
resourceArn="arn:aws:codepipeline:us-east-1:123456789012:not-existing"
)
ex = e.exception
ex.operation_name.should.equal("ListTagsForResource")
ex.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
ex.response["Error"]["Code"].should.contain("ResourceNotFoundException")
ex.response["Error"]["Message"].should.equal(
"The account with id '123456789012' does not include a pipeline with the name 'not-existing'"
)
@mock_codepipeline
def test_tag_resource():
client = boto3.client("codepipeline", region_name="us-east-1")
name = "test-pipeline"
create_basic_codepipeline(client, name)
client.tag_resource(
resourceArn="arn:aws:codepipeline:us-east-1:123456789012:{}".format(name),
tags=[{"key": "key-2", "value": "value-2"}],
)
response = client.list_tags_for_resource(
resourceArn="arn:aws:codepipeline:us-east-1:123456789012:{}".format(name)
)
response["tags"].should.equal(
[{"key": "key", "value": "value"}, {"key": "key-2", "value": "value-2"}]
)
@mock_codepipeline
def test_tag_resource_errors():
client = boto3.client("codepipeline", region_name="us-east-1")
name = "test-pipeline"
create_basic_codepipeline(client, name)
with assert_raises(ClientError) as e:
client.tag_resource(
resourceArn="arn:aws:codepipeline:us-east-1:123456789012:not-existing",
tags=[{"key": "key-2", "value": "value-2"}],
)
ex = e.exception
ex.operation_name.should.equal("TagResource")
ex.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
ex.response["Error"]["Code"].should.contain("ResourceNotFoundException")
ex.response["Error"]["Message"].should.equal(
"The account with id '123456789012' does not include a pipeline with the name 'not-existing'"
)
with assert_raises(ClientError) as e:
client.tag_resource(
resourceArn="arn:aws:codepipeline:us-east-1:123456789012:{}".format(name),
tags=[{"key": "aws:key", "value": "value"}],
)
ex = e.exception
ex.operation_name.should.equal("TagResource")
ex.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
ex.response["Error"]["Code"].should.contain("InvalidTagsException")
ex.response["Error"]["Message"].should.equal(
"Not allowed to modify system tags. "
"System tags start with 'aws:'. "
"msg=[Caller is an end user and not allowed to mutate system tags]"
)
with assert_raises(ClientError) as e:
client.tag_resource(
resourceArn="arn:aws:codepipeline:us-east-1:123456789012:{}".format(name),
tags=[
{"key": "key-{}".format(i), "value": "value-{}".format(i)}
for i in range(50)
],
)
ex = e.exception
ex.operation_name.should.equal("TagResource")
ex.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
ex.response["Error"]["Code"].should.contain("TooManyTagsException")
ex.response["Error"]["Message"].should.equal(
"Tag limit exceeded for resource [arn:aws:codepipeline:us-east-1:123456789012:{}].".format(
name
)
)
@mock_codepipeline
def test_untag_resource():
client = boto3.client("codepipeline", region_name="us-east-1")
name = "test-pipeline"
create_basic_codepipeline(client, name)
response = client.list_tags_for_resource(
resourceArn="arn:aws:codepipeline:us-east-1:123456789012:{}".format(name)
)
response["tags"].should.equal([{"key": "key", "value": "value"}])
client.untag_resource(
resourceArn="arn:aws:codepipeline:us-east-1:123456789012:{}".format(name),
tagKeys=["key"],
)
response = client.list_tags_for_resource(
resourceArn="arn:aws:codepipeline:us-east-1:123456789012:{}".format(name)
)
response["tags"].should.have.length_of(0)
# removing a not existing tag should raise no exception
client.untag_resource(
resourceArn="arn:aws:codepipeline:us-east-1:123456789012:{}".format(name),
tagKeys=["key"],
)
@mock_codepipeline
def test_untag_resource_errors():
client = boto3.client("codepipeline", region_name="us-east-1")
with assert_raises(ClientError) as e:
client.untag_resource(
resourceArn="arn:aws:codepipeline:us-east-1:123456789012:not-existing",
tagKeys=["key"],
)
ex = e.exception
ex.operation_name.should.equal("UntagResource")
ex.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
ex.response["Error"]["Code"].should.contain("ResourceNotFoundException")
ex.response["Error"]["Message"].should.equal(
"The account with id '123456789012' does not include a pipeline with the name 'not-existing'"
)
@mock_iam
def get_role_arn():
iam = boto3.client("iam", region_name="us-east-1")
client = boto3.client("iam", region_name="us-east-1")
try:
return iam.get_role(RoleName="test-role")["Role"]["Arn"]
return client.get_role(RoleName="test-role")["Role"]["Arn"]
except ClientError:
return iam.create_role(
return client.create_role(
RoleName="test-role",
AssumeRolePolicyDocument=json.dumps(
{
@ -875,3 +669,52 @@ def get_role_arn():
}
),
)["Role"]["Arn"]
def create_basic_codepipeline(client, name):
return client.create_pipeline(
pipeline={
"name": name,
"roleArn": get_role_arn(),
"artifactStore": {
"type": "S3",
"location": "codepipeline-us-east-1-123456789012",
},
"stages": [
{
"name": "Stage-1",
"actions": [
{
"name": "Action-1",
"actionTypeId": {
"category": "Source",
"owner": "AWS",
"provider": "S3",
"version": "1",
},
"configuration": {
"S3Bucket": "test-bucket",
"S3ObjectKey": "test-object",
},
"outputArtifacts": [{"name": "artifact"},],
},
],
},
{
"name": "Stage-2",
"actions": [
{
"name": "Action-1",
"actionTypeId": {
"category": "Approval",
"owner": "AWS",
"provider": "Manual",
"version": "1",
},
},
],
},
],
},
tags=[{"key": "key", "value": "value"}],
)

View File

@ -1142,11 +1142,13 @@ def test_token_legitimacy():
id_claims = json.loads(jws.verify(id_token, json_web_key, "RS256"))
id_claims["iss"].should.equal(issuer)
id_claims["aud"].should.equal(client_id)
id_claims["token_use"].should.equal("id")
for k, v in outputs["additional_fields"].items():
id_claims[k].should.equal(v)
access_claims = json.loads(jws.verify(access_token, json_web_key, "RS256"))
access_claims["iss"].should.equal(issuer)
access_claims["aud"].should.equal(client_id)
for k, v in outputs["additional_fields"].items():
access_claims[k].should.equal(v)
access_claims["token_use"].should.equal("access")
@mock_cognitoidp

View File

@ -9,7 +9,7 @@ from boto3.dynamodb.conditions import Attr, Key
import sure # noqa
import requests
from moto import mock_dynamodb2, mock_dynamodb2_deprecated
from moto.dynamodb2 import dynamodb_backend2
from moto.dynamodb2 import dynamodb_backend2, dynamodb_backends2
from boto.exception import JSONResponseError
from botocore.exceptions import ClientError, ParamValidationError
from tests.helpers import requires_boto_gte
@ -350,6 +350,60 @@ def test_put_item_with_special_chars():
)
@requires_boto_gte("2.9")
@mock_dynamodb2
def test_put_item_with_streams():
name = "TestTable"
conn = boto3.client(
"dynamodb",
region_name="us-west-2",
aws_access_key_id="ak",
aws_secret_access_key="sk",
)
conn.create_table(
TableName=name,
KeySchema=[{"AttributeName": "forum_name", "KeyType": "HASH"}],
AttributeDefinitions=[{"AttributeName": "forum_name", "AttributeType": "S"}],
StreamSpecification={
"StreamEnabled": True,
"StreamViewType": "NEW_AND_OLD_IMAGES",
},
ProvisionedThroughput={"ReadCapacityUnits": 5, "WriteCapacityUnits": 5},
)
conn.put_item(
TableName=name,
Item={
"forum_name": {"S": "LOLCat Forum"},
"subject": {"S": "Check this out!"},
"Body": {"S": "http://url_to_lolcat.gif"},
"SentBy": {"S": "test"},
"Data": {"M": {"Key1": {"S": "Value1"}, "Key2": {"S": "Value2"}}},
},
)
result = conn.get_item(TableName=name, Key={"forum_name": {"S": "LOLCat Forum"}})
result["Item"].should.be.equal(
{
"forum_name": {"S": "LOLCat Forum"},
"subject": {"S": "Check this out!"},
"Body": {"S": "http://url_to_lolcat.gif"},
"SentBy": {"S": "test"},
"Data": {"M": {"Key1": {"S": "Value1"}, "Key2": {"S": "Value2"}}},
}
)
table = dynamodb_backends2["us-west-2"].get_table(name)
if not table:
# There is no way to access stream data over the API, so this part can't run in server-tests mode.
return
len(table.stream_shard.items).should.be.equal(1)
stream_record = table.stream_shard.items[0].record
stream_record["eventName"].should.be.equal("INSERT")
stream_record["dynamodb"]["SizeBytes"].should.be.equal(447)
@requires_boto_gte("2.9")
@mock_dynamodb2
def test_query_returns_consumed_capacity():
@ -1665,6 +1719,32 @@ def test_scan_filter4():
assert response["Count"] == 0
@mock_dynamodb2
def test_scan_filter_should_not_return_non_existing_attributes():
table_name = "my-table"
item = {"partitionKey": "pk-2", "my-attr": 42}
# Create table
res = boto3.resource("dynamodb", region_name="us-east-1")
res.create_table(
TableName=table_name,
KeySchema=[{"AttributeName": "partitionKey", "KeyType": "HASH"}],
AttributeDefinitions=[{"AttributeName": "partitionKey", "AttributeType": "S"}],
BillingMode="PAY_PER_REQUEST",
)
table = res.Table(table_name)
# Insert items
table.put_item(Item={"partitionKey": "pk-1"})
table.put_item(Item=item)
# Verify a few operations
# Assert we only find the item that has this attribute
table.scan(FilterExpression=Attr("my-attr").lt(43))["Items"].should.equal([item])
table.scan(FilterExpression=Attr("my-attr").lte(42))["Items"].should.equal([item])
table.scan(FilterExpression=Attr("my-attr").gte(42))["Items"].should.equal([item])
table.scan(FilterExpression=Attr("my-attr").gt(41))["Items"].should.equal([item])
# Sanity check that we can't find the item if the FE is wrong
table.scan(FilterExpression=Attr("my-attr").gt(43))["Items"].should.equal([])
@mock_dynamodb2
def test_bad_scan_filter():
client = boto3.client("dynamodb", region_name="us-east-1")
@ -2451,6 +2531,48 @@ def test_condition_expressions():
)
@mock_dynamodb2
def test_condition_expression_numerical_attribute():
dynamodb = boto3.resource("dynamodb", region_name="us-east-1")
dynamodb.create_table(
TableName="my-table",
KeySchema=[{"AttributeName": "partitionKey", "KeyType": "HASH"}],
AttributeDefinitions=[{"AttributeName": "partitionKey", "AttributeType": "S"}],
)
table = dynamodb.Table("my-table")
table.put_item(Item={"partitionKey": "pk-pos", "myAttr": 5})
table.put_item(Item={"partitionKey": "pk-neg", "myAttr": -5})
# try to update the item we put in the table using numerical condition expression
# Specifically, verify that we can compare with a zero-value
# First verify that > and >= work on positive numbers
update_numerical_con_expr(
key="pk-pos", con_expr="myAttr > :zero", res="6", table=table
)
update_numerical_con_expr(
key="pk-pos", con_expr="myAttr >= :zero", res="7", table=table
)
# Second verify that < and <= work on negative numbers
update_numerical_con_expr(
key="pk-neg", con_expr="myAttr < :zero", res="-4", table=table
)
update_numerical_con_expr(
key="pk-neg", con_expr="myAttr <= :zero", res="-3", table=table
)
def update_numerical_con_expr(key, con_expr, res, table):
table.update_item(
Key={"partitionKey": key},
UpdateExpression="ADD myAttr :one",
ExpressionAttributeValues={":zero": 0, ":one": 1},
ConditionExpression=con_expr,
)
table.get_item(Key={"partitionKey": key})["Item"]["myAttr"].should.equal(
Decimal(res)
)
@mock_dynamodb2
def test_condition_expression__attr_doesnt_exist():
client = boto3.client("dynamodb", region_name="us-east-1")
@ -3435,6 +3557,58 @@ def test_update_supports_nested_list_append_onto_another_list():
)
@mock_dynamodb2
def test_update_supports_list_append_maps():
client = boto3.client("dynamodb", region_name="us-west-1")
client.create_table(
AttributeDefinitions=[
{"AttributeName": "id", "AttributeType": "S"},
{"AttributeName": "rid", "AttributeType": "S"},
],
TableName="TestTable",
KeySchema=[
{"AttributeName": "id", "KeyType": "HASH"},
{"AttributeName": "rid", "KeyType": "RANGE"},
],
ProvisionedThroughput={"ReadCapacityUnits": 5, "WriteCapacityUnits": 5},
)
client.put_item(
TableName="TestTable",
Item={
"id": {"S": "nested_list_append"},
"rid": {"S": "range_key"},
"a": {"L": [{"M": {"b": {"S": "bar1"}}}]},
},
)
# Update item using list_append expression
client.update_item(
TableName="TestTable",
Key={"id": {"S": "nested_list_append"}, "rid": {"S": "range_key"}},
UpdateExpression="SET a = list_append(a, :i)",
ExpressionAttributeValues={":i": {"L": [{"M": {"b": {"S": "bar2"}}}]}},
)
# Verify item is appended to the existing list
result = client.query(
TableName="TestTable",
KeyConditionExpression="id = :i AND begins_with(rid, :r)",
ExpressionAttributeValues={
":i": {"S": "nested_list_append"},
":r": {"S": "range_key"},
},
)["Items"]
result.should.equal(
[
{
"a": {"L": [{"M": {"b": {"S": "bar1"}}}, {"M": {"b": {"S": "bar2"}}}]},
"rid": {"S": "range_key"},
"id": {"S": "nested_list_append"},
}
]
)
@mock_dynamodb2
def test_update_catches_invalid_list_append_operation():
client = boto3.client("dynamodb", region_name="us-east-1")

View File

@ -11,7 +11,7 @@ from moto import mock_ec2, mock_ec2_deprecated
def test_describe_regions():
conn = boto.connect_ec2("the_key", "the_secret")
regions = conn.get_all_regions()
regions.should.have.length_of(16)
len(regions).should.be.greater_than(1)
for region in regions:
region.endpoint.should.contain(region.name)
@ -32,7 +32,7 @@ def test_availability_zones():
def test_boto3_describe_regions():
ec2 = boto3.client("ec2", "us-east-1")
resp = ec2.describe_regions()
resp["Regions"].should.have.length_of(16)
len(resp["Regions"]).should.be.greater_than(1)
for rec in resp["Regions"]:
rec["Endpoint"].should.contain(rec["RegionName"])

View File

@ -3,13 +3,21 @@ import boto.ec2
import boto.ec2.autoscale
import boto.ec2.elb
import sure
from boto3 import Session
from moto import mock_ec2_deprecated, mock_autoscaling_deprecated, mock_elb_deprecated
from moto.ec2 import ec2_backends
def test_use_boto_regions():
boto_regions = {r.name for r in boto.ec2.regions()}
boto_regions = set()
for region in Session().get_available_regions("ec2"):
boto_regions.add(region)
for region in Session().get_available_regions("ec2", partition_name="aws-us-gov"):
boto_regions.add(region)
for region in Session().get_available_regions("ec2", partition_name="aws-cn"):
boto_regions.add(region)
moto_regions = set(ec2_backends)
moto_regions.should.equal(boto_regions)

View File

@ -0,0 +1,23 @@
import boto3
from moto import mock_ec2_instance_connect
pubkey = """ssh-rsa
AAAAB3NzaC1yc2EAAAADAQABAAABAQDV5+voluw2zmzqpqCAqtsyoP01TQ8Ydx1eS1yD6wUsHcPqMIqpo57YxiC8XPwrdeKQ6GG6MC3bHsgXoPypGP0LyixbiuLTU31DnnqorcHt4bWs6rQa7dK2pCCflz2fhYRt5ZjqSNsAKivIbqkH66JozN0SySIka3kEV79GdB0BicioKeEJlCwM9vvxafyzjWf/z8E0lh4ni3vkLpIVJ0t5l+Qd9QMJrT6Is0SCQPVagTYZoi8+fWDoGsBa8vyRwDjEzBl28ZplKh9tSyDkRIYszWTpmK8qHiqjLYZBfAxXjGJbEYL1iig4ZxvbYzKEiKSBi1ZMW9iWjHfZDZuxXAmB
example
"""
@mock_ec2_instance_connect
def test_send_ssh_public_key():
client = boto3.client("ec2-instance-connect", region_name="us-east-1")
fake_request_id = "example-2a47-4c91-9700-e37e85162cb6"
response = client.send_ssh_public_key(
InstanceId="i-abcdefg12345",
InstanceOSUser="ec2-user",
SSHPublicKey=pubkey,
AvailabilityZone="us-east-1a",
)
assert response["RequestId"] == fake_request_id

View File

@ -94,6 +94,7 @@ def test_register_task_definition():
"logConfiguration": {"logDriver": "json-file"},
}
],
networkMode="bridge",
tags=[
{"key": "createdBy", "value": "moto-unittest"},
{"key": "foo", "value": "bar"},
@ -124,6 +125,7 @@ def test_register_task_definition():
response["taskDefinition"]["containerDefinitions"][0]["logConfiguration"][
"logDriver"
].should.equal("json-file")
response["taskDefinition"]["networkMode"].should.equal("bridge")
@mock_ecs

View File

@ -44,7 +44,7 @@ def test_describe_job():
joboutput.should.have.key("Tier").which.should.equal("Standard")
joboutput.should.have.key("StatusCode").which.should.equal("InProgress")
joboutput.should.have.key("VaultARN").which.should.equal(
"arn:aws:glacier:RegionInfo:us-west-2:012345678901:vaults/my_vault"
"arn:aws:glacier:us-west-2:012345678901:vaults/my_vault"
)

View File

@ -1737,9 +1737,7 @@ def test_delete_saml_provider():
def test_create_role_defaults():
"""Tests default values"""
conn = boto3.client("iam", region_name="us-east-1")
conn.create_role(
RoleName="my-role", AssumeRolePolicyDocument="{}",
)
conn.create_role(RoleName="my-role", AssumeRolePolicyDocument="{}")
# Get role:
role = conn.get_role(RoleName="my-role")["Role"]
@ -2672,3 +2670,33 @@ def test_get_account_summary():
"GroupsQuota": 300,
}
)
@mock_iam()
def test_list_user_tags():
"""Tests both setting a tags on a user in create_user and list_user_tags"""
conn = boto3.client("iam", region_name="us-east-1")
conn.create_user(UserName="kenny-bania")
conn.create_user(
UserName="jackie-chiles", Tags=[{"Key": "Sue-Allen", "Value": "Oh-Henry"}]
)
conn.create_user(
UserName="cosmo",
Tags=[
{"Key": "Stan", "Value": "The Caddy"},
{"Key": "like-a", "Value": "glove"},
],
)
response = conn.list_user_tags(UserName="kenny-bania")
response["Tags"].should.equal([])
response["IsTruncated"].should_not.be.ok
response = conn.list_user_tags(UserName="jackie-chiles")
response["Tags"].should.equal([{"Key": "Sue-Allen", "Value": "Oh-Henry"}])
response["IsTruncated"].should_not.be.ok
response = conn.list_user_tags(UserName="cosmo")
response["Tags"].should.equal(
[{"Key": "Stan", "Value": "The Caddy"}, {"Key": "like-a", "Value": "glove"}]
)
response["IsTruncated"].should_not.be.ok

View File

@ -862,6 +862,8 @@ def test_list_resource_record_sets_name_type_filters():
StartRecordName=all_records[start_with][1],
)
response["IsTruncated"].should.equal(False)
returned_records = [
(record["Type"], record["Name"]) for record in response["ResourceRecordSets"]
]

View File

@ -1261,7 +1261,7 @@ def test_boto3_list_objects_truncated_response():
assert listed_object["Key"] == "one"
assert resp["MaxKeys"] == 1
assert resp["IsTruncated"] == True
assert resp["Prefix"] == "None"
assert resp.get("Prefix") is None
assert resp["Delimiter"] == "None"
assert "NextMarker" in resp
@ -1274,7 +1274,7 @@ def test_boto3_list_objects_truncated_response():
assert listed_object["Key"] == "three"
assert resp["MaxKeys"] == 1
assert resp["IsTruncated"] == True
assert resp["Prefix"] == "None"
assert resp.get("Prefix") is None
assert resp["Delimiter"] == "None"
assert "NextMarker" in resp
@ -1287,7 +1287,7 @@ def test_boto3_list_objects_truncated_response():
assert listed_object["Key"] == "two"
assert resp["MaxKeys"] == 1
assert resp["IsTruncated"] == False
assert resp["Prefix"] == "None"
assert resp.get("Prefix") is None
assert resp["Delimiter"] == "None"
assert "NextMarker" not in resp

View File

@ -54,9 +54,10 @@ def test_deleting_subscriptions_by_deleting_topic():
]["Subscriptions"]
subscriptions.should.have.length_of(1)
subscription = subscriptions[0]
subscription_arn = subscription["SubscriptionArn"]
subscription["TopicArn"].should.equal(topic_arn)
subscription["Protocol"].should.equal("http")
subscription["SubscriptionArn"].should.contain(topic_arn)
subscription_arn.should.contain(topic_arn)
subscription["Endpoint"].should.equal("http://example.com/")
# Now delete the topic
@ -67,12 +68,25 @@ def test_deleting_subscriptions_by_deleting_topic():
topics = topics_json["ListTopicsResponse"]["ListTopicsResult"]["Topics"]
topics.should.have.length_of(0)
# And there should be zero subscriptions left
# And the subscription should still be left
subscriptions = conn.get_all_subscriptions()["ListSubscriptionsResponse"][
"ListSubscriptionsResult"
]["Subscriptions"]
subscriptions.should.have.length_of(1)
subscription = subscriptions[0]
subscription["SubscriptionArn"].should.equal(subscription_arn)
# Now delete hanging subscription
conn.unsubscribe(subscription_arn)
subscriptions = conn.get_all_subscriptions()["ListSubscriptionsResponse"][
"ListSubscriptionsResult"
]["Subscriptions"]
subscriptions.should.have.length_of(0)
# Deleting it again should not result in any error
conn.unsubscribe(subscription_arn)
@mock_sns_deprecated
def test_getting_subscriptions_by_topic():

View File

@ -97,34 +97,48 @@ def test_creating_subscription():
@mock_sns
def test_deleting_subscriptions_by_deleting_topic():
conn = boto3.client("sns", region_name="us-east-1")
conn.create_topic(Name="some-topic")
response = conn.list_topics()
def test_unsubscribe_from_deleted_topic():
client = boto3.client("sns", region_name="us-east-1")
client.create_topic(Name="some-topic")
response = client.list_topics()
topic_arn = response["Topics"][0]["TopicArn"]
conn.subscribe(TopicArn=topic_arn, Protocol="http", Endpoint="http://example.com/")
client.subscribe(
TopicArn=topic_arn, Protocol="http", Endpoint="http://example.com/"
)
subscriptions = conn.list_subscriptions()["Subscriptions"]
subscriptions = client.list_subscriptions()["Subscriptions"]
subscriptions.should.have.length_of(1)
subscription = subscriptions[0]
subscription_arn = subscription["SubscriptionArn"]
subscription["TopicArn"].should.equal(topic_arn)
subscription["Protocol"].should.equal("http")
subscription["SubscriptionArn"].should.contain(topic_arn)
subscription_arn.should.contain(topic_arn)
subscription["Endpoint"].should.equal("http://example.com/")
# Now delete the topic
conn.delete_topic(TopicArn=topic_arn)
client.delete_topic(TopicArn=topic_arn)
# And there should now be 0 topics
topics_json = conn.list_topics()
topics_json = client.list_topics()
topics = topics_json["Topics"]
topics.should.have.length_of(0)
# And there should be zero subscriptions left
subscriptions = conn.list_subscriptions()["Subscriptions"]
# And the subscription should still be left
subscriptions = client.list_subscriptions()["Subscriptions"]
subscriptions.should.have.length_of(1)
subscription = subscriptions[0]
subscription["SubscriptionArn"].should.equal(subscription_arn)
# Now delete hanging subscription
client.unsubscribe(SubscriptionArn=subscription_arn)
subscriptions = client.list_subscriptions()["Subscriptions"]
subscriptions.should.have.length_of(0)
# Deleting it again should not result in any error
client.unsubscribe(SubscriptionArn=subscription_arn)
@mock_sns
def test_getting_subscriptions_by_topic():

View File

@ -331,7 +331,20 @@ def test_delete_queue():
@mock_sqs
def test_get_queue_attributes():
client = boto3.client("sqs", region_name="us-east-1")
response = client.create_queue(QueueName="test-queue")
dlq_resp = client.create_queue(QueueName="test-dlr-queue")
dlq_arn1 = client.get_queue_attributes(QueueUrl=dlq_resp["QueueUrl"])["Attributes"][
"QueueArn"
]
response = client.create_queue(
QueueName="test-queue",
Attributes={
"RedrivePolicy": json.dumps(
{"deadLetterTargetArn": dlq_arn1, "maxReceiveCount": 2}
),
},
)
queue_url = response["QueueUrl"]
response = client.get_queue_attributes(QueueUrl=queue_url)
@ -356,6 +369,7 @@ def test_get_queue_attributes():
"ApproximateNumberOfMessages",
"MaximumMessageSize",
"QueueArn",
"RedrivePolicy",
"VisibilityTimeout",
],
)
@ -366,6 +380,9 @@ def test_get_queue_attributes():
"MaximumMessageSize": "65536",
"QueueArn": "arn:aws:sqs:us-east-1:{}:test-queue".format(ACCOUNT_ID),
"VisibilityTimeout": "30",
"RedrivePolicy": json.dumps(
{"deadLetterTargetArn": dlq_arn1, "maxReceiveCount": 2}
),
}
)