Merge pull request #25 from spulec/master

Merge upstream
This commit is contained in:
Bert Blommers 2020-01-04 09:06:23 +00:00 committed by GitHub
commit 74ad2d7d3c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
132 changed files with 4351 additions and 642 deletions

File diff suppressed because it is too large Load Diff

View File

@ -9,6 +9,8 @@ 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
from .cognitoidp import mock_cognitoidp, mock_cognitoidp_deprecated # noqa
@ -20,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

@ -13,8 +13,9 @@ import cryptography.hazmat.primitives.asymmetric.rsa
from cryptography.hazmat.primitives import serialization, hashes
from cryptography.hazmat.backends import default_backend
from moto.core import ACCOUNT_ID as DEFAULT_ACCOUNT_ID
DEFAULT_ACCOUNT_ID = 123456789012
GOOGLE_ROOT_CA = b"""-----BEGIN CERTIFICATE-----
MIIEKDCCAxCgAwIBAgIQAQAhJYiw+lmnd+8Fe2Yn3zANBgkqhkiG9w0BAQsFADBC
MQswCQYDVQQGEwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEbMBkGA1UEAxMS

View File

@ -101,3 +101,12 @@ class ApiKeyNotFoundException(RESTError):
super(ApiKeyNotFoundException, self).__init__(
"NotFoundException", "Invalid API Key identifier specified"
)
class ApiKeyAlreadyExists(RESTError):
code = 409
def __init__(self):
super(ApiKeyAlreadyExists, self).__init__(
"ConflictException", "API Key already exists"
)

View File

@ -32,6 +32,7 @@ from .exceptions import (
RoleNotSpecified,
NoIntegrationDefined,
NoMethodDefined,
ApiKeyAlreadyExists,
)
STAGE_URL = "https://{api_id}.execute-api.{region_name}.amazonaws.com/{stage_name}"
@ -759,6 +760,10 @@ class APIGatewayBackend(BaseBackend):
return api.delete_deployment(deployment_id)
def create_apikey(self, payload):
if payload.get("value") is not None:
for api_key in self.get_apikeys():
if api_key.get("value") == payload["value"]:
raise ApiKeyAlreadyExists()
key = ApiKey(**payload)
self.keys[key["id"]] = key
return key
@ -842,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

@ -9,6 +9,7 @@ from .exceptions import (
BadRequestException,
CrossAccountNotAllowed,
StageNotFoundException,
ApiKeyAlreadyExists,
)
@ -302,7 +303,17 @@ class APIGatewayResponse(BaseResponse):
self.setup_class(request, full_url, headers)
if self.method == "POST":
try:
apikey_response = self.backend.create_apikey(json.loads(self.body))
except ApiKeyAlreadyExists as error:
return (
error.code,
self.headers,
'{{"message":"{0}","code":"{1}"}}'.format(
error.message, error.error_type
),
)
elif self.method == "GET":
apikeys_response = self.backend.get_apikeys()
return 200, {}, json.dumps({"item": apikeys_response})

View File

@ -1,10 +1,11 @@
from __future__ import unicode_literals
import time
import boto3
from boto3 import Session
from moto.core import BaseBackend, BaseModel
ACCOUNT_ID = 123456789012
from moto.core import ACCOUNT_ID
class TaggableResourceMixin(object):
@ -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
@ -42,11 +43,10 @@ from .utils import make_function_arn, make_function_ver_arn
from moto.sqs import sqs_backends
from moto.dynamodb2 import dynamodb_backends2
from moto.dynamodbstreams import dynamodbstreams_backends
from moto.core import ACCOUNT_ID
logger = logging.getLogger(__name__)
ACCOUNT_ID = "123456789012"
try:
from tempfile import TemporaryDirectory
@ -1044,10 +1044,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,8 @@ 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
from moto.config import config_backends
@ -18,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
@ -60,6 +63,8 @@ BACKENDS = {
"batch": batch_backends,
"cloudformation": cloudformation_backends,
"cloudwatch": cloudwatch_backends,
"codecommit": codecommit_backends,
"codepipeline": codepipeline_backends,
"cognito-identity": cognitoidentity_backends,
"cognito-idp": cognitoidp_backends,
"config": config_backends,
@ -69,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
@ -28,11 +29,10 @@ from .utils import (
from moto.ec2.exceptions import InvalidSubnetIdError
from moto.ec2.models import INSTANCE_TYPES as EC2_INSTANCE_TYPES
from moto.iam.exceptions import IAMNotFoundException
from moto.core import ACCOUNT_ID as DEFAULT_ACCOUNT_ID
_orig_adapter_send = requests.adapters.HTTPAdapter.send
logger = logging.getLogger(__name__)
DEFAULT_ACCOUNT_ID = 123456789012
COMPUTE_ENVIRONMENT_NAME_REGEX = re.compile(
r"^[A-Za-z0-9][A-Za-z0-9_-]{1,126}[A-Za-z0-9]$"
)
@ -182,7 +182,7 @@ class JobDefinition(BaseModel):
self._region = region_name
self.container_properties = container_properties
self.arn = None
self.status = "INACTIVE"
self.status = "ACTIVE"
if parameters is None:
parameters = {}
@ -285,7 +285,7 @@ class JobDefinition(BaseModel):
class Job(threading.Thread, BaseModel):
def __init__(self, name, job_def, job_queue, log_backend):
def __init__(self, name, job_def, job_queue, log_backend, container_overrides):
"""
Docker Job
@ -301,6 +301,7 @@ class Job(threading.Thread, BaseModel):
self.job_name = name
self.job_id = str(uuid.uuid4())
self.job_definition = job_def
self.container_overrides = container_overrides
self.job_queue = job_queue
self.job_state = "SUBMITTED" # One of SUBMITTED | PENDING | RUNNABLE | STARTING | RUNNING | SUCCEEDED | FAILED
self.job_queue.jobs.append(self)
@ -357,6 +358,11 @@ class Job(threading.Thread, BaseModel):
result["statusReason"] = self.job_stopped_reason
return result
def _get_container_property(self, p, default):
return self.container_overrides.get(
p, self.job_definition.container_properties.get(p, default)
)
def run(self):
"""
Run the container.
@ -375,8 +381,33 @@ class Job(threading.Thread, BaseModel):
self.job_state = "PENDING"
time.sleep(1)
image = "alpine:latest"
cmd = '/bin/sh -c "for a in `seq 1 10`; do echo Hello World; sleep 1; done"'
image = self.job_definition.container_properties.get(
"image", "alpine:latest"
)
privileged = self.job_definition.container_properties.get(
"privileged", False
)
cmd = self._get_container_property(
"command",
'/bin/sh -c "for a in `seq 1 10`; do echo Hello World; sleep 1; done"',
)
environment = {
e["name"]: e["value"]
for e in self._get_container_property("environment", [])
}
volumes = {
v["name"]: v["host"]
for v in self._get_container_property("volumes", [])
}
mounts = [
docker.types.Mount(
m["containerPath"],
volumes[m["sourceVolume"]]["sourcePath"],
type="bind",
read_only=m["readOnly"],
)
for m in self._get_container_property("mountPoints", [])
]
name = "{0}-{1}".format(self.job_name, self.job_id)
self.job_state = "RUNNABLE"
@ -384,8 +415,16 @@ class Job(threading.Thread, BaseModel):
time.sleep(1)
self.job_state = "STARTING"
log_config = docker.types.LogConfig(type=docker.types.LogConfig.types.JSON)
container = self.docker_client.containers.run(
image, cmd, detach=True, name=name
image,
cmd,
detach=True,
name=name,
log_config=log_config,
environment=environment,
mounts=mounts,
privileged=privileged,
)
self.job_state = "RUNNING"
self.job_started_at = datetime.datetime.now()
@ -815,8 +854,10 @@ class BatchBackend(BaseBackend):
raise InvalidParameterValueException(
"computeResources must contain {0}".format(param)
)
if self.iam_backend.get_role_by_arn(cr["instanceRole"]) is None:
for profile in self.iam_backend.get_instance_profiles():
if profile.arn == cr["instanceRole"]:
break
else:
raise InvalidParameterValueException(
"could not find instanceRole {0}".format(cr["instanceRole"])
)
@ -1208,7 +1249,13 @@ class BatchBackend(BaseBackend):
if queue is None:
raise ClientException("Job queue {0} does not exist".format(job_queue))
job = Job(job_name, job_def, queue, log_backend=self.logs_backend)
job = Job(
job_name,
job_def,
queue,
log_backend=self.logs_backend,
container_overrides=container_overrides,
)
self._jobs[job.job_id] = job
# Here comes the fun
@ -1271,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,5 +1,4 @@
from __future__ import unicode_literals
import collections
import functools
import logging
import copy
@ -11,6 +10,7 @@ from moto.awslambda import models as lambda_models
from moto.batch import models as batch_models
from moto.cloudwatch import models as cloudwatch_models
from moto.cognitoidentity import models as cognitoidentity_models
from moto.compat import collections_abc
from moto.datapipeline import models as datapipeline_models
from moto.dynamodb2 import models as dynamodb2_models
from moto.ec2 import models as ec2_models
@ -27,6 +27,7 @@ from moto.route53 import models as route53_models
from moto.s3 import models as s3_models
from moto.sns import models as sns_models
from moto.sqs import models as sqs_models
from moto.core import ACCOUNT_ID
from .utils import random_suffix
from .exceptions import (
ExportNotFound,
@ -404,7 +405,7 @@ def parse_output(output_logical_id, output_json, resources_map):
return output
class ResourceMap(collections.Mapping):
class ResourceMap(collections_abc.Mapping):
"""
This is a lazy loading map for resources. This allows us to create resources
without needing to create a full dependency tree. Upon creation, each
@ -431,7 +432,7 @@ class ResourceMap(collections.Mapping):
# Create the default resources
self._parsed_resources = {
"AWS::AccountId": "123456789012",
"AWS::AccountId": ACCOUNT_ID,
"AWS::Region": self._region_name,
"AWS::StackId": stack_id,
"AWS::StackName": stack_name,
@ -633,7 +634,7 @@ class ResourceMap(collections.Mapping):
raise last_exception
class OutputMap(collections.Mapping):
class OutputMap(collections_abc.Mapping):
def __init__(self, resources, template, stack_id):
self._template = template
self._stack_id = stack_id

View File

@ -7,6 +7,7 @@ from six.moves.urllib.parse import urlparse
from moto.core.responses import BaseResponse
from moto.core.utils import amzn_request_id
from moto.s3 import s3_backend
from moto.core import ACCOUNT_ID
from .models import cloudformation_backends
from .exceptions import ValidationError
@ -425,7 +426,9 @@ class CloudFormationResponse(BaseResponse):
stackset = self.cloudformation_backend.get_stack_set(stackset_name)
if not stackset.admin_role:
stackset.admin_role = "arn:aws:iam::123456789012:role/AWSCloudFormationStackSetAdministrationRole"
stackset.admin_role = "arn:aws:iam::{AccountId}:role/AWSCloudFormationStackSetAdministrationRole".format(
AccountId=ACCOUNT_ID
)
if not stackset.execution_role:
stackset.execution_role = "AWSCloudFormationStackSetExecutionRole"
@ -1051,11 +1054,14 @@ STOP_STACK_SET_OPERATION_RESPONSE_TEMPLATE = """<StopStackSetOperationResponse x
</ResponseMetadata> </StopStackSetOperationResponse>
"""
DESCRIBE_STACKSET_OPERATION_RESPONSE_TEMPLATE = """<DescribeStackSetOperationResponse xmlns="http://internal.amazon.com/coral/com.amazonaws.maestro.service.v20160713/">
DESCRIBE_STACKSET_OPERATION_RESPONSE_TEMPLATE = (
"""<DescribeStackSetOperationResponse xmlns="http://internal.amazon.com/coral/com.amazonaws.maestro.service.v20160713/">
<DescribeStackSetOperationResult>
<StackSetOperation>
<ExecutionRoleName>{{ stackset.execution_role }}</ExecutionRoleName>
<AdministrationRoleARN>arn:aws:iam::123456789012:role/{{ stackset.admin_role }}</AdministrationRoleARN>
<AdministrationRoleARN>arn:aws:iam::"""
+ ACCOUNT_ID
+ """:role/{{ stackset.admin_role }}</AdministrationRoleARN>
<StackSetId>{{ stackset.id }}</StackSetId>
<CreationTimestamp>{{ operation.CreationTimestamp }}</CreationTimestamp>
<OperationId>{{ operation.OperationId }}</OperationId>
@ -1072,15 +1078,19 @@ DESCRIBE_STACKSET_OPERATION_RESPONSE_TEMPLATE = """<DescribeStackSetOperationRes
</ResponseMetadata>
</DescribeStackSetOperationResponse>
"""
)
LIST_STACK_SET_OPERATION_RESULTS_RESPONSE_TEMPLATE = """<ListStackSetOperationResultsResponse xmlns="http://internal.amazon.com/coral/com.amazonaws.maestro.service.v20160713/">
LIST_STACK_SET_OPERATION_RESULTS_RESPONSE_TEMPLATE = (
"""<ListStackSetOperationResultsResponse xmlns="http://internal.amazon.com/coral/com.amazonaws.maestro.service.v20160713/">
<ListStackSetOperationResultsResult>
<Summaries>
{% for instance in operation.Instances %}
{% for account, region in instance.items() %}
<member>
<AccountGateResult>
<StatusReason>Function not found: arn:aws:lambda:us-west-2:123456789012:function:AWSCloudFormationStackSetAccountGate</StatusReason>
<StatusReason>Function not found: arn:aws:lambda:us-west-2:"""
+ ACCOUNT_ID
+ """:function:AWSCloudFormationStackSetAccountGate</StatusReason>
<Status>SKIPPED</Status>
</AccountGateResult>
<Region>{{ region }}</Region>
@ -1096,3 +1106,4 @@ LIST_STACK_SET_OPERATION_RESULTS_RESPONSE_TEMPLATE = """<ListStackSetOperationRe
</ResponseMetadata>
</ListStackSetOperationResultsResponse>
"""
)

View File

@ -7,6 +7,7 @@ import os
import string
from cfnlint import decode, core
from moto.core import ACCOUNT_ID
def generate_stack_id(stack_name, region="us-east-1", account="123456789"):
@ -29,8 +30,8 @@ def generate_stackset_id(stackset_name):
def generate_stackset_arn(stackset_id, region_name):
return "arn:aws:cloudformation:{}:123456789012:stackset/{}".format(
region_name, stackset_id
return "arn:aws:cloudformation:{}:{}:stackset/{}".format(
region_name, ACCOUNT_ID, stackset_id
)

View File

@ -1,14 +1,16 @@
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
from .utils import make_arn_for_dashboard
DEFAULT_ACCOUNT_ID = 123456789012
from moto.core import ACCOUNT_ID as DEFAULT_ACCOUNT_ID
_EMPTY_LIST = tuple()
@ -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

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

View File

@ -0,0 +1,44 @@
from moto.core.exceptions import JsonRESTError
class InvalidStructureException(JsonRESTError):
code = 400
def __init__(self, message):
super(InvalidStructureException, self).__init__(
"InvalidStructureException", message
)
class PipelineNotFoundException(JsonRESTError):
code = 400
def __init__(self, message):
super(PipelineNotFoundException, self).__init__(
"PipelineNotFoundException", message
)
class ResourceNotFoundException(JsonRESTError):
code = 400
def __init__(self, message):
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)
)

218
moto/codepipeline/models.py Normal file
View File

@ -0,0 +1,218 @@
import json
from datetime import datetime
from boto3 import Session
from moto.core.utils import iso_8601_datetime_with_milliseconds
from moto.iam.exceptions import IAMNotFoundException
from moto.iam import iam_backends
from moto.codepipeline.exceptions import (
InvalidStructureException,
PipelineNotFoundException,
ResourceNotFoundException,
InvalidTagsException,
TooManyTagsException,
)
from moto.core import BaseBackend, BaseModel
from moto.iam.models import ACCOUNT_ID
class CodePipeline(BaseModel):
def __init__(self, region, pipeline):
# the version number for a new pipeline is always 1
pipeline["version"] = 1
self.pipeline = self.add_default_values(pipeline)
self.tags = {}
self._arn = "arn:aws:codepipeline:{0}:{1}:{2}".format(
region, ACCOUNT_ID, pipeline["name"]
)
self._created = datetime.utcnow()
self._updated = datetime.utcnow()
@property
def metadata(self):
return {
"pipelineArn": self._arn,
"created": iso_8601_datetime_with_milliseconds(self._created),
"updated": iso_8601_datetime_with_milliseconds(self._updated),
}
def add_default_values(self, pipeline):
for stage in pipeline["stages"]:
for action in stage["actions"]:
if "runOrder" not in action:
action["runOrder"] = 1
if "configuration" not in action:
action["configuration"] = {}
if "outputArtifacts" not in action:
action["outputArtifacts"] = []
if "inputArtifacts" not in action:
action["inputArtifacts"] = []
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):
self.pipelines = {}
@property
def iam_backend(self):
return iam_backends["global"]
def create_pipeline(self, region, pipeline, tags):
if pipeline["name"] in self.pipelines:
raise InvalidStructureException(
"A pipeline with the name '{0}' already exists in account '{1}'".format(
pipeline["name"], ACCOUNT_ID
)
)
try:
role = self.iam_backend.get_role_by_arn(pipeline["roleArn"])
service_principal = json.loads(role.assume_role_policy_document)[
"Statement"
][0]["Principal"]["Service"]
if "codepipeline.amazonaws.com" not in service_principal:
raise IAMNotFoundException("")
except IAMNotFoundException:
raise InvalidStructureException(
"CodePipeline is not authorized to perform AssumeRole on role {}".format(
pipeline["roleArn"]
)
)
if len(pipeline["stages"]) < 2:
raise InvalidStructureException(
"Pipeline has only 1 stage(s). There should be a minimum of 2 stages in a pipeline"
)
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, sorted(tags, key=lambda i: i["key"])
def get_pipeline(self, name):
codepipeline = self.pipelines.get(name)
if not codepipeline:
raise PipelineNotFoundException(
"Account '{0}' does not have a pipeline with name '{1}'".format(
ACCOUNT_ID, name
)
)
return codepipeline.pipeline, codepipeline.metadata
def update_pipeline(self, pipeline):
codepipeline = self.pipelines.get(pipeline["name"])
if not codepipeline:
raise ResourceNotFoundException(
"The account with id '{0}' does not include a pipeline with the name '{1}'".format(
ACCOUNT_ID, pipeline["name"]
)
)
# version number is auto incremented
pipeline["version"] = codepipeline.pipeline["version"] + 1
codepipeline._updated = datetime.utcnow()
codepipeline.pipeline = codepipeline.add_default_values(pipeline)
return codepipeline.pipeline
def list_pipelines(self):
pipelines = []
for name, codepipeline in self.pipelines.items():
pipelines.append(
{
"name": name,
"version": codepipeline.pipeline["version"],
"created": codepipeline.metadata["created"],
"updated": codepipeline.metadata["updated"],
}
)
return sorted(pipelines, key=lambda i: i["name"])
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

@ -0,0 +1,62 @@
import json
from moto.core.responses import BaseResponse
from .models import codepipeline_backends
class CodePipelineResponse(BaseResponse):
@property
def codepipeline_backend(self):
return codepipeline_backends[self.region]
def create_pipeline(self):
pipeline, tags = self.codepipeline_backend.create_pipeline(
self.region, self._get_param("pipeline"), self._get_param("tags")
)
return json.dumps({"pipeline": pipeline, "tags": tags})
def get_pipeline(self):
pipeline, metadata = self.codepipeline_backend.get_pipeline(
self._get_param("name")
)
return json.dumps({"pipeline": pipeline, "metadata": metadata})
def update_pipeline(self):
pipeline = self.codepipeline_backend.update_pipeline(
self._get_param("pipeline")
)
return json.dumps({"pipeline": pipeline})
def list_pipelines(self):
pipelines = self.codepipeline_backend.list_pipelines()
return json.dumps({"pipelines": pipelines})
def delete_pipeline(self):
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

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

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
@ -749,8 +749,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

@ -3,3 +3,8 @@ try:
except ImportError:
# python 2.6 or earlier, use backport
from ordereddict import OrderedDict # noqa
try:
import collections.abc as collections_abc # noqa
except ImportError:
import collections as collections_abc # noqa

View File

@ -45,7 +45,8 @@ from moto.config.exceptions import (
from moto.core import BaseBackend, BaseModel
from moto.s3.config import s3_config_query
DEFAULT_ACCOUNT_ID = "123456789012"
from moto.core import ACCOUNT_ID as DEFAULT_ACCOUNT_ID
POP_STRINGS = [
"capitalizeStart",
"CapitalizeStart",
@ -1083,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,6 +1,6 @@
from __future__ import unicode_literals
from .models import BaseModel, BaseBackend, moto_api_backend # noqa
from .models import BaseModel, BaseBackend, moto_api_backend, ACCOUNT_ID # noqa
from .responses import ActionAuthenticatorMixin
moto_api_backends = {"global": moto_api_backend}

View File

@ -24,7 +24,8 @@ from botocore.awsrequest import AWSRequest
from botocore.credentials import Credentials
from six import string_types
from moto.iam.models import ACCOUNT_ID, Policy
from moto.core import ACCOUNT_ID
from moto.iam.models import Policy
from moto.iam import iam_backend
from moto.core.exceptions import (
SignatureDoesNotMatchError,

View File

@ -23,6 +23,9 @@ from .utils import (
)
ACCOUNT_ID = os.environ.get("MOTO_ACCOUNT_ID", "123456789012")
class BaseMockAWS(object):
nested_count = 0

View File

@ -304,3 +304,27 @@ def path_url(url):
if parsed_url.query:
path = path + "?" + parsed_url.query
return path
def py2_strip_unicode_keys(blob):
"""For Python 2 Only -- this will convert unicode keys in nested Dicts, Lists, and Sets to standard strings."""
if type(blob) == unicode: # noqa
return str(blob)
elif type(blob) == dict:
for key in list(blob.keys()):
value = blob.pop(key)
blob[str(key)] = py2_strip_unicode_keys(value)
elif type(blob) == list:
for i in range(0, len(blob)):
blob[i] = py2_strip_unicode_keys(blob[i])
elif type(blob) == set:
new_set = set()
for value in blob:
new_set.add(py2_strip_unicode_keys(value))
blob = new_set
return blob

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,5 +1,5 @@
import collections
import six
from moto.compat import collections_abc
from moto.core.utils import get_random_hex
@ -8,13 +8,13 @@ def get_random_pipeline_id():
def remove_capitalization_of_dict_keys(obj):
if isinstance(obj, collections.Mapping):
if isinstance(obj, collections_abc.Mapping):
result = obj.__class__()
for key, value in obj.items():
normalized_key = key[:1].lower() + key[1:]
result[normalized_key] = remove_capitalization_of_dict_keys(value)
return result
elif isinstance(obj, collections.Iterable) and not isinstance(
elif isinstance(obj, collections_abc.Iterable) and not isinstance(
obj, six.string_types
):
result = obj.__class__()

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

@ -6,6 +6,7 @@ import json
from moto.compat import OrderedDict
from moto.core import BaseBackend, BaseModel
from moto.core.utils import unix_time
from moto.core import ACCOUNT_ID
from .comparisons import get_comparison_func
@ -277,8 +278,8 @@ class Table(BaseModel):
if attribute_name == "StreamArn":
region = "us-east-1"
time = "2000-01-01T00:00:00.000"
return "arn:aws:dynamodb:{0}:123456789012:table/{1}/stream/{2}".format(
region, self.name, time
return "arn:aws:dynamodb:{0}:{1}:table/{2}/stream/{3}".format(
region, ACCOUNT_ID, self.name, time
)
raise UnformattedGetAttTemplateException()

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
@ -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

@ -438,9 +438,12 @@ class DynamoHandler(BaseResponse):
all_indexes = (table.global_indexes or []) + (table.indexes or [])
indexes_by_name = dict((i["IndexName"], i) for i in all_indexes)
if index_name not in indexes_by_name:
raise ValueError(
"Invalid index: %s for table: %s. Available indexes are: %s"
% (index_name, name, ", ".join(indexes_by_name.keys()))
er = "com.amazonaws.dynamodb.v20120810#ResourceNotFoundException"
return self.error(
er,
"Invalid index: {} for table: {}. Available indexes are: {}".format(
index_name, name, ", ".join(indexes_by_name.keys())
),
)
index = indexes_by_name[index_name]["KeySchema"]
@ -481,7 +484,9 @@ class DynamoHandler(BaseResponse):
]
elif "begins_with" in range_key_expression:
range_comparison = "BEGINS_WITH"
range_values = [value_alias_map[range_key_expression_components[1]]]
range_values = [
value_alias_map[range_key_expression_components[-1]]
]
else:
range_values = [value_alias_map[range_key_expression_components[2]]]
else:

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=[]):
@ -2449,6 +2493,7 @@ class VPC(TaggedEC2Resource):
self.is_default = "true" if is_default else "false"
self.enable_dns_support = "true"
self.classic_link_enabled = "false"
self.classic_link_dns_supported = "false"
# This attribute is set to 'true' only for default VPCs
# or VPCs created using the wizard of the VPC console
self.enable_dns_hostnames = "true" if is_default else "false"
@ -3306,6 +3351,7 @@ class Route(object):
local=False,
gateway=None,
instance=None,
nat_gateway=None,
interface=None,
vpc_pcx=None,
):
@ -3315,6 +3361,7 @@ class Route(object):
self.local = local
self.gateway = gateway
self.instance = instance
self.nat_gateway = nat_gateway
self.interface = interface
self.vpc_pcx = vpc_pcx
@ -3327,6 +3374,7 @@ class Route(object):
gateway_id = properties.get("GatewayId")
instance_id = properties.get("InstanceId")
interface_id = properties.get("NetworkInterfaceId")
nat_gateway_id = properties.get("NatGatewayId")
pcx_id = properties.get("VpcPeeringConnectionId")
route_table_id = properties["RouteTableId"]
@ -3336,6 +3384,7 @@ class Route(object):
destination_cidr_block=properties.get("DestinationCidrBlock"),
gateway_id=gateway_id,
instance_id=instance_id,
nat_gateway_id=nat_gateway_id,
interface_id=interface_id,
vpc_peering_connection_id=pcx_id,
)
@ -3353,6 +3402,7 @@ class RouteBackend(object):
local=False,
gateway_id=None,
instance_id=None,
nat_gateway_id=None,
interface_id=None,
vpc_peering_connection_id=None,
):
@ -3373,12 +3423,17 @@ class RouteBackend(object):
except ValueError:
raise InvalidDestinationCIDRBlockParameterError(destination_cidr_block)
nat_gateway = None
if nat_gateway_id is not None:
nat_gateway = self.nat_gateways.get(nat_gateway_id)
route = Route(
route_table,
destination_cidr_block,
local=local,
gateway=gateway,
instance=self.get_instance(instance_id) if instance_id else None,
nat_gateway=nat_gateway,
interface=None,
vpc_pcx=self.get_vpc_peering_connection(vpc_peering_connection_id)
if vpc_peering_connection_id
@ -4827,7 +4882,35 @@ class NatGatewayBackend(object):
super(NatGatewayBackend, self).__init__()
def get_all_nat_gateways(self, filters):
return self.nat_gateways.values()
nat_gateways = self.nat_gateways.values()
if filters is not None:
if filters.get("nat-gateway-id") is not None:
nat_gateways = [
nat_gateway
for nat_gateway in nat_gateways
if nat_gateway.id in filters["nat-gateway-id"]
]
if filters.get("vpc-id") is not None:
nat_gateways = [
nat_gateway
for nat_gateway in nat_gateways
if nat_gateway.vpc_id in filters["vpc-id"]
]
if filters.get("subnet-id") is not None:
nat_gateways = [
nat_gateway
for nat_gateway in nat_gateways
if nat_gateway.subnet_id in filters["subnet-id"]
]
if filters.get("state") is not None:
nat_gateways = [
nat_gateway
for nat_gateway in nat_gateways
if nat_gateway.state in filters["state"]
]
return nat_gateways
def create_nat_gateway(self, subnet_id, allocation_id):
nat_gateway = NatGateway(self, subnet_id, allocation_id)

View File

@ -6,6 +6,7 @@ from moto.core.responses import BaseResponse
from moto.core.utils import camelcase_to_underscores
from moto.ec2.utils import filters_from_querystring, dict_from_querystring
from moto.elbv2 import elbv2_backends
from moto.core import ACCOUNT_ID
class InstanceResponse(BaseResponse):
@ -246,10 +247,13 @@ class InstanceResponse(BaseResponse):
return EC2_MODIFY_INSTANCE_ATTRIBUTE
EC2_RUN_INSTANCES = """<RunInstancesResponse xmlns="http://ec2.amazonaws.com/doc/2013-10-15/">
EC2_RUN_INSTANCES = (
"""<RunInstancesResponse xmlns="http://ec2.amazonaws.com/doc/2013-10-15/">
<requestId>59dbff89-35bd-4eac-99ed-be587EXAMPLE</requestId>
<reservationId>{{ reservation.id }}</reservationId>
<ownerId>123456789012</ownerId>
<ownerId>"""
+ ACCOUNT_ID
+ """</ownerId>
<groupSet>
<item>
<groupId>sg-245f6a01</groupId>
@ -331,7 +335,9 @@ EC2_RUN_INSTANCES = """<RunInstancesResponse xmlns="http://ec2.amazonaws.com/doc
<vpcId>{{ nic.subnet.vpc_id }}</vpcId>
{% endif %}
<description>Primary network interface</description>
<ownerId>123456789012</ownerId>
<ownerId>"""
+ ACCOUNT_ID
+ """</ownerId>
<status>in-use</status>
<macAddress>1b:2b:3c:4d:5e:6f</macAddress>
<privateIpAddress>{{ nic.private_ip_address }}</privateIpAddress>
@ -354,7 +360,9 @@ EC2_RUN_INSTANCES = """<RunInstancesResponse xmlns="http://ec2.amazonaws.com/doc
{% if nic.public_ip %}
<association>
<publicIp>{{ nic.public_ip }}</publicIp>
<ipOwnerId>123456789012</ipOwnerId>
<ipOwnerId>"""
+ ACCOUNT_ID
+ """</ipOwnerId>
</association>
{% endif %}
<privateIpAddressesSet>
@ -364,7 +372,9 @@ EC2_RUN_INSTANCES = """<RunInstancesResponse xmlns="http://ec2.amazonaws.com/doc
{% if nic.public_ip %}
<association>
<publicIp>{{ nic.public_ip }}</publicIp>
<ipOwnerId>123456789012</ipOwnerId>
<ipOwnerId>"""
+ ACCOUNT_ID
+ """</ipOwnerId>
</association>
{% endif %}
</item>
@ -376,14 +386,18 @@ EC2_RUN_INSTANCES = """<RunInstancesResponse xmlns="http://ec2.amazonaws.com/doc
{% endfor %}
</instancesSet>
</RunInstancesResponse>"""
)
EC2_DESCRIBE_INSTANCES = """<DescribeInstancesResponse xmlns="http://ec2.amazonaws.com/doc/2013-10-15/">
EC2_DESCRIBE_INSTANCES = (
"""<DescribeInstancesResponse xmlns="http://ec2.amazonaws.com/doc/2013-10-15/">
<requestId>fdcdcab1-ae5c-489e-9c33-4637c5dda355</requestId>
<reservationSet>
{% for reservation in reservations %}
<item>
<reservationId>{{ reservation.id }}</reservationId>
<ownerId>123456789012</ownerId>
<ownerId>"""
+ ACCOUNT_ID
+ """</ownerId>
<groupSet>
{% for group in reservation.dynamic_group_list %}
<item>
@ -476,7 +490,9 @@ EC2_DESCRIBE_INSTANCES = """<DescribeInstancesResponse xmlns="http://ec2.amazona
{% endfor %}
</blockDeviceMapping>
<virtualizationType>{{ instance.virtualization_type }}</virtualizationType>
<clientToken>ABCDE1234567890123</clientToken>
<clientToken>ABCDE"""
+ ACCOUNT_ID
+ """3</clientToken>
{% if instance.get_tags() %}
<tagSet>
{% for tag in instance.get_tags() %}
@ -499,7 +515,9 @@ EC2_DESCRIBE_INSTANCES = """<DescribeInstancesResponse xmlns="http://ec2.amazona
<vpcId>{{ nic.subnet.vpc_id }}</vpcId>
{% endif %}
<description>Primary network interface</description>
<ownerId>123456789012</ownerId>
<ownerId>"""
+ ACCOUNT_ID
+ """</ownerId>
<status>in-use</status>
<macAddress>1b:2b:3c:4d:5e:6f</macAddress>
<privateIpAddress>{{ nic.private_ip_address }}</privateIpAddress>
@ -526,7 +544,9 @@ EC2_DESCRIBE_INSTANCES = """<DescribeInstancesResponse xmlns="http://ec2.amazona
{% if nic.public_ip %}
<association>
<publicIp>{{ nic.public_ip }}</publicIp>
<ipOwnerId>123456789012</ipOwnerId>
<ipOwnerId>"""
+ ACCOUNT_ID
+ """</ipOwnerId>
</association>
{% endif %}
<privateIpAddressesSet>
@ -536,7 +556,9 @@ EC2_DESCRIBE_INSTANCES = """<DescribeInstancesResponse xmlns="http://ec2.amazona
{% if nic.public_ip %}
<association>
<publicIp>{{ nic.public_ip }}</publicIp>
<ipOwnerId>123456789012</ipOwnerId>
<ipOwnerId>"""
+ ACCOUNT_ID
+ """</ipOwnerId>
</association>
{% endif %}
</item>
@ -554,6 +576,7 @@ EC2_DESCRIBE_INSTANCES = """<DescribeInstancesResponse xmlns="http://ec2.amazona
<nextToken>{{ next_token }}</nextToken>
{% endif %}
</DescribeInstancesResponse>"""
)
EC2_TERMINATE_INSTANCES = """
<TerminateInstancesResponse xmlns="http://ec2.amazonaws.com/doc/2013-10-15/">

View File

@ -18,6 +18,7 @@ class RouteTables(BaseResponse):
destination_cidr_block = self._get_param("DestinationCidrBlock")
gateway_id = self._get_param("GatewayId")
instance_id = self._get_param("InstanceId")
nat_gateway_id = self._get_param("NatGatewayId")
interface_id = self._get_param("NetworkInterfaceId")
pcx_id = self._get_param("VpcPeeringConnectionId")
@ -26,6 +27,7 @@ class RouteTables(BaseResponse):
destination_cidr_block,
gateway_id=gateway_id,
instance_id=instance_id,
nat_gateway_id=nat_gateway_id,
interface_id=interface_id,
vpc_peering_connection_id=pcx_id,
)
@ -173,6 +175,10 @@ DESCRIBE_ROUTE_TABLES_RESPONSE = """
<origin>CreateRoute</origin>
<state>blackhole</state>
{% endif %}
{% if route.nat_gateway %}
<natGatewayId>{{ route.nat_gateway.id }}</natGatewayId>
<state>active</state>
{% endif %}
</item>
{% endfor %}
</routeSet>

View File

@ -2,6 +2,7 @@ from __future__ import unicode_literals
from moto.core.responses import BaseResponse
from moto.ec2.utils import filters_from_querystring
from moto.core import ACCOUNT_ID
def try_parse_int(value, default=None):
@ -171,12 +172,15 @@ DELETE_GROUP_RESPONSE = """<DeleteSecurityGroupResponse xmlns="http://ec2.amazon
<return>true</return>
</DeleteSecurityGroupResponse>"""
DESCRIBE_SECURITY_GROUPS_RESPONSE = """<DescribeSecurityGroupsResponse xmlns="http://ec2.amazonaws.com/doc/2013-10-15/">
DESCRIBE_SECURITY_GROUPS_RESPONSE = (
"""<DescribeSecurityGroupsResponse xmlns="http://ec2.amazonaws.com/doc/2013-10-15/">
<requestId>59dbff89-35bd-4eac-99ed-be587EXAMPLE</requestId>
<securityGroupInfo>
{% for group in groups %}
<item>
<ownerId>123456789012</ownerId>
<ownerId>"""
+ ACCOUNT_ID
+ """</ownerId>
<groupId>{{ group.id }}</groupId>
<groupName>{{ group.name }}</groupName>
<groupDescription>{{ group.description }}</groupDescription>
@ -196,7 +200,9 @@ DESCRIBE_SECURITY_GROUPS_RESPONSE = """<DescribeSecurityGroupsResponse xmlns="ht
<groups>
{% for source_group in rule.source_groups %}
<item>
<userId>123456789012</userId>
<userId>"""
+ ACCOUNT_ID
+ """</userId>
<groupId>{{ source_group.id }}</groupId>
<groupName>{{ source_group.name }}</groupName>
</item>
@ -225,7 +231,9 @@ DESCRIBE_SECURITY_GROUPS_RESPONSE = """<DescribeSecurityGroupsResponse xmlns="ht
<groups>
{% for source_group in rule.source_groups %}
<item>
<userId>123456789012</userId>
<userId>"""
+ ACCOUNT_ID
+ """</userId>
<groupId>{{ source_group.id }}</groupId>
<groupName>{{ source_group.name }}</groupName>
</item>
@ -255,6 +263,7 @@ DESCRIBE_SECURITY_GROUPS_RESPONSE = """<DescribeSecurityGroupsResponse xmlns="ht
{% endfor %}
</securityGroupInfo>
</DescribeSecurityGroupsResponse>"""
)
AUTHORIZE_SECURITY_GROUP_INGRESS_REPONSE = """<AuthorizeSecurityGroupIngressResponse xmlns="http://ec2.amazonaws.com/doc/2013-10-15/">
<requestId>59dbff89-35bd-4eac-99ed-be587EXAMPLE</requestId>

View File

@ -1,5 +1,6 @@
from __future__ import unicode_literals
from moto.core.responses import BaseResponse
from moto.core import ACCOUNT_ID
class VPCPeeringConnections(BaseResponse):
@ -40,7 +41,8 @@ class VPCPeeringConnections(BaseResponse):
return template.render()
CREATE_VPC_PEERING_CONNECTION_RESPONSE = """
CREATE_VPC_PEERING_CONNECTION_RESPONSE = (
"""
<CreateVpcPeeringConnectionResponse xmlns="http://ec2.amazonaws.com/doc/2016-11-15/">
<requestId>7a62c49f-347e-4fc4-9331-6e8eEXAMPLE</requestId>
<vpcPeeringConnection>
@ -56,7 +58,9 @@ CREATE_VPC_PEERING_CONNECTION_RESPONSE = """
</peeringOptions>
</requesterVpcInfo>
<accepterVpcInfo>
<ownerId>123456789012</ownerId>
<ownerId>"""
+ ACCOUNT_ID
+ """</ownerId>
<vpcId>{{ vpc_pcx.peer_vpc.id }}</vpcId>
</accepterVpcInfo>
<status>
@ -68,8 +72,10 @@ CREATE_VPC_PEERING_CONNECTION_RESPONSE = """
</vpcPeeringConnection>
</CreateVpcPeeringConnectionResponse>
"""
)
DESCRIBE_VPC_PEERING_CONNECTIONS_RESPONSE = """
DESCRIBE_VPC_PEERING_CONNECTIONS_RESPONSE = (
"""
<DescribeVpcPeeringConnectionsResponse xmlns="http://ec2.amazonaws.com/doc/2016-11-15/">
<requestId>7a62c49f-347e-4fc4-9331-6e8eEXAMPLE</requestId>
<vpcPeeringConnectionSet>
@ -82,7 +88,9 @@ DESCRIBE_VPC_PEERING_CONNECTIONS_RESPONSE = """
<cidrBlock>{{ vpc_pcx.vpc.cidr_block }}</cidrBlock>
</requesterVpcInfo>
<accepterVpcInfo>
<ownerId>123456789012</ownerId>
<ownerId>"""
+ ACCOUNT_ID
+ """</ownerId>
<vpcId>{{ vpc_pcx.peer_vpc.id }}</vpcId>
<cidrBlock>{{ vpc_pcx.peer_vpc.cidr_block }}</cidrBlock>
<peeringOptions>
@ -101,6 +109,7 @@ DESCRIBE_VPC_PEERING_CONNECTIONS_RESPONSE = """
</vpcPeeringConnectionSet>
</DescribeVpcPeeringConnectionsResponse>
"""
)
DELETE_VPC_PEERING_CONNECTION_RESPONSE = """
<DeleteVpcPeeringConnectionResponse xmlns="http://ec2.amazonaws.com/doc/2013-10-15/">
@ -109,7 +118,8 @@ DELETE_VPC_PEERING_CONNECTION_RESPONSE = """
</DeleteVpcPeeringConnectionResponse>
"""
ACCEPT_VPC_PEERING_CONNECTION_RESPONSE = """
ACCEPT_VPC_PEERING_CONNECTION_RESPONSE = (
"""
<AcceptVpcPeeringConnectionResponse xmlns="http://ec2.amazonaws.com/doc/2016-11-15/">
<requestId>7a62c49f-347e-4fc4-9331-6e8eEXAMPLE</requestId>
<vpcPeeringConnection>
@ -120,7 +130,9 @@ ACCEPT_VPC_PEERING_CONNECTION_RESPONSE = """
<cidrBlock>{{ vpc_pcx.vpc.cidr_block }}</cidrBlock>
</requesterVpcInfo>
<accepterVpcInfo>
<ownerId>123456789012</ownerId>
<ownerId>"""
+ ACCOUNT_ID
+ """</ownerId>
<vpcId>{{ vpc_pcx.peer_vpc.id }}</vpcId>
<cidrBlock>{{ vpc_pcx.peer_vpc.cidr_block }}</cidrBlock>
<peeringOptions>
@ -137,6 +149,7 @@ ACCEPT_VPC_PEERING_CONNECTION_RESPONSE = """
</vpcPeeringConnection>
</AcceptVpcPeeringConnectionResponse>
"""
)
REJECT_VPC_PEERING_CONNECTION_RESPONSE = """
<RejectVpcPeeringConnectionResponse xmlns="http://ec2.amazonaws.com/doc/2013-10-15/">

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
@ -1302,7 +1303,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

@ -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

@ -14,7 +14,7 @@ from cryptography.hazmat.backends import default_backend
from six.moves.urllib.parse import urlparse
from moto.core.exceptions import RESTError
from moto.core import BaseBackend, BaseModel
from moto.core import BaseBackend, BaseModel, ACCOUNT_ID
from moto.core.utils import (
iso_8601_datetime_without_milliseconds,
iso_8601_datetime_with_milliseconds,
@ -45,8 +45,6 @@ from .utils import (
random_policy_id,
)
ACCOUNT_ID = 123456789012
class MFADevice(object):
"""MFA Device class."""
@ -309,6 +307,7 @@ class Role(BaseModel):
permissions_boundary,
description,
tags,
max_session_duration,
):
self.id = role_id
self.name = name
@ -320,6 +319,7 @@ class Role(BaseModel):
self.tags = tags
self.description = description
self.permissions_boundary = permissions_boundary
self.max_session_duration = max_session_duration
@property
def created_iso_8601(self):
@ -338,6 +338,7 @@ class Role(BaseModel):
permissions_boundary=properties.get("PermissionsBoundary", ""),
description=properties.get("Description", ""),
tags=properties.get("Tags", {}),
max_session_duration=properties.get("MaxSessionDuration", 3600),
)
policies = properties.get("Policies", [])
@ -542,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 "/"
@ -555,6 +556,7 @@ class User(BaseModel):
self.password = None
self.password_reset_required = False
self.signing_certificates = {}
self.tags = tags
@property
def arn(self):
@ -938,9 +940,10 @@ class IAMBackend(BaseBackend):
role.description = role_description
return role
def update_role(self, role_name, role_description):
def update_role(self, role_name, role_description, max_session_duration):
role = self.get_role(role_name)
role.description = role_description
role.max_session_duration = max_session_duration
return role
def detach_role_policy(self, policy_arn, role_name):
@ -1059,6 +1062,7 @@ class IAMBackend(BaseBackend):
permissions_boundary,
description,
tags,
max_session_duration,
):
role_id = random_resource_id()
if permissions_boundary and not self.policy_arn_regex.match(
@ -1084,6 +1088,7 @@ class IAMBackend(BaseBackend):
permissions_boundary,
description,
clean_tags,
max_session_duration,
)
self.roles[role_id] = role
return role
@ -1417,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
@ -1579,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

@ -182,6 +182,7 @@ class IamResponse(BaseResponse):
permissions_boundary = self._get_param("PermissionsBoundary")
description = self._get_param("Description")
tags = self._get_multi_param("Tags.member")
max_session_duration = self._get_param("MaxSessionDuration", 3600)
role = iam_backend.create_role(
role_name,
@ -190,6 +191,7 @@ class IamResponse(BaseResponse):
permissions_boundary,
description,
tags,
max_session_duration,
)
template = self.response_template(CREATE_ROLE_TEMPLATE)
return template.render(role=role)
@ -258,7 +260,8 @@ class IamResponse(BaseResponse):
def update_role(self):
role_name = self._get_param("RoleName")
description = self._get_param("Description")
role = iam_backend.update_role(role_name, description)
max_session_duration = self._get_param("MaxSessionDuration", 3600)
role = iam_backend.update_role(role_name, description, max_session_duration)
template = self.response_template(UPDATE_ROLE_TEMPLATE)
return template.render(role=role)
@ -437,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)
@ -535,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")
@ -1189,9 +1198,12 @@ CREATE_ROLE_TEMPLATE = """<CreateRoleResponse xmlns="https://iam.amazonaws.com/d
<Arn>{{ role.arn }}</Arn>
<RoleName>{{ role.name }}</RoleName>
<AssumeRolePolicyDocument>{{ role.assume_role_policy_document }}</AssumeRolePolicyDocument>
{% if role.description %}
<Description>{{role.description}}</Description>
{% endif %}
<CreateDate>{{ role.created_iso_8601 }}</CreateDate>
<RoleId>{{ role.id }}</RoleId>
<MaxSessionDuration>{{ role.max_session_duration }}</MaxSessionDuration>
{% if role.permissions_boundary %}
<PermissionsBoundary>
<PermissionsBoundaryType>PermissionsBoundaryPolicy</PermissionsBoundaryType>
@ -1244,6 +1256,7 @@ UPDATE_ROLE_DESCRIPTION_TEMPLATE = """<UpdateRoleDescriptionResponse xmlns="http
<Description>{{role.description}}</Description>
<CreateDate>{{ role.created_iso_8601 }}</CreateDate>
<RoleId>{{ role.id }}</RoleId>
<MaxSessionDuration>{{ role.max_session_duration }}</MaxSessionDuration>
{% if role.tags %}
<Tags>
{% for tag in role.get_tags() %}
@ -1268,9 +1281,12 @@ GET_ROLE_TEMPLATE = """<GetRoleResponse xmlns="https://iam.amazonaws.com/doc/201
<Arn>{{ role.arn }}</Arn>
<RoleName>{{ role.name }}</RoleName>
<AssumeRolePolicyDocument>{{ role.assume_role_policy_document }}</AssumeRolePolicyDocument>
{% if role.description %}
<Description>{{role.description}}</Description>
{% endif %}
<CreateDate>{{ role.created_iso_8601 }}</CreateDate>
<RoleId>{{ role.id }}</RoleId>
<MaxSessionDuration>{{ role.max_session_duration }}</MaxSessionDuration>
{% if role.tags %}
<Tags>
{% for tag in role.get_tags() %}
@ -1689,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 (
@ -825,5 +825,10 @@ class IoTBackend(BaseBackend):
return self.jobs[job_id]
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,6 +2,7 @@ from __future__ import unicode_literals
import json
from werkzeug.exceptions import BadRequest
from moto.core import ACCOUNT_ID
class ResourceNotFoundError(BadRequest):
@ -23,14 +24,14 @@ class ResourceInUseError(BadRequest):
class StreamNotFoundError(ResourceNotFoundError):
def __init__(self, stream_name):
super(StreamNotFoundError, self).__init__(
"Stream {0} under account 123456789012 not found.".format(stream_name)
"Stream {0} under account {1} not found.".format(stream_name, ACCOUNT_ID)
)
class ShardNotFoundError(ResourceNotFoundError):
def __init__(self, shard_id):
super(ShardNotFoundError, self).__init__(
"Shard {0} under account 123456789012 not found.".format(shard_id)
"Shard {0} under account {1} not found.".format(shard_id, ACCOUNT_ID)
)

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,9 +9,12 @@ 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
from moto.core import ACCOUNT_ID
from .exceptions import (
StreamNotFoundError,
ShardNotFoundError,
@ -133,7 +135,7 @@ class Stream(BaseModel):
self.shard_count = shard_count
self.creation_datetime = datetime.datetime.now()
self.region = region
self.account_number = "123456789012"
self.account_number = ACCOUNT_ID
self.shards = {}
self.tags = {}
self.status = "ACTIVE"
@ -259,8 +261,8 @@ class DeliveryStream(BaseModel):
@property
def arn(self):
return "arn:aws:firehose:us-east-1:123456789012:deliverystream/{0}".format(
self.name
return "arn:aws:firehose:us-east-1:{1}:deliverystream/{0}".format(
self.name, ACCOUNT_ID
)
def destinations_to_dict(self):
@ -529,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

@ -1,6 +1,7 @@
from __future__ import unicode_literals
from moto.core import BaseBackend, BaseModel
from moto.ec2 import ec2_backends
from moto.core import ACCOUNT_ID
import uuid
import datetime
from random import choice
@ -367,7 +368,7 @@ class Stack(BaseModel):
self.id = "{0}".format(uuid.uuid4())
self.layers = []
self.apps = []
self.account_number = "123456789012"
self.account_number = ACCOUNT_ID
self.created_at = datetime.datetime.utcnow()
def __eq__(self, other):

View File

@ -2,8 +2,10 @@ from __future__ import unicode_literals
import random
import string
from moto.core import ACCOUNT_ID
MASTER_ACCOUNT_ID = "123456789012"
MASTER_ACCOUNT_ID = ACCOUNT_ID
MASTER_ACCOUNT_EMAIL = "master@example.com"
DEFAULT_POLICY_ID = "p-FullAWSAccess"
ORGANIZATION_ARN_FORMAT = "arn:aws:organizations::{0}:organization/{1}"

View File

@ -2,13 +2,14 @@ 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
from .utils import make_arn_for_lexicon
DEFAULT_ACCOUNT_ID = 123456789012
from moto.core import ACCOUNT_ID as DEFAULT_ACCOUNT_ID
class Lexicon(BaseModel):
@ -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
@ -27,7 +27,7 @@ from .exceptions import (
)
ACCOUNT_ID = 123456789012
from moto.core import ACCOUNT_ID
class TaggableResourceMixin(object):
@ -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,11 +1,13 @@
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
@ -23,8 +25,8 @@ class FakeResourceGroup(BaseModel):
if self._validate_tags(value=tags):
self._tags = tags
self._raise_errors()
self.arn = "arn:aws:resource-groups:us-west-1:123456789012:{name}".format(
name=name
self.arn = "arn:aws:resource-groups:us-west-1:{AccountId}:{name}".format(
name=name, AccountId=ACCOUNT_ID
)
@staticmethod
@ -349,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

@ -323,3 +323,27 @@ class BucketSignatureDoesNotMatchError(S3ClientError):
*args,
**kwargs
)
class NoSuchPublicAccessBlockConfiguration(S3ClientError):
code = 404
def __init__(self, *args, **kwargs):
super(NoSuchPublicAccessBlockConfiguration, self).__init__(
"NoSuchPublicAccessBlockConfiguration",
"The public access block configuration was not found",
*args,
**kwargs
)
class InvalidPublicAccessBlockConfiguration(S3ClientError):
code = 400
def __init__(self, *args, **kwargs):
super(InvalidPublicAccessBlockConfiguration, self).__init__(
"InvalidRequest",
"Must specify at least one configuration.",
*args,
**kwargs
)

View File

@ -35,6 +35,8 @@ from .exceptions import (
InvalidTargetBucketForLogging,
DuplicateTagKeys,
CrossLocationLoggingProhibitted,
NoSuchPublicAccessBlockConfiguration,
InvalidPublicAccessBlockConfiguration,
)
from .utils import clean_key_name, _VersionedKeyStore
@ -659,11 +661,8 @@ class Notification(BaseModel):
else:
data["filter"] = None
data[
"objectPrefixes"
] = (
[]
) # Not sure why this is a thing since AWS just seems to return this as filters ¯\_(ツ)_/¯
# Not sure why this is a thing since AWS just seems to return this as filters ¯\_(ツ)_/¯
data["objectPrefixes"] = []
return data
@ -728,6 +727,38 @@ class NotificationConfiguration(BaseModel):
return data
def convert_str_to_bool(item):
"""Converts a boolean string to a boolean value"""
if isinstance(item, str):
return item.lower() == "true"
return False
class PublicAccessBlock(BaseModel):
def __init__(
self,
block_public_acls,
ignore_public_acls,
block_public_policy,
restrict_public_buckets,
):
# The boto XML appears to expect these values to exist as lowercase strings...
self.block_public_acls = block_public_acls or "false"
self.ignore_public_acls = ignore_public_acls or "false"
self.block_public_policy = block_public_policy or "false"
self.restrict_public_buckets = restrict_public_buckets or "false"
def to_config_dict(self):
# Need to make the string values booleans for Config:
return {
"blockPublicAcls": convert_str_to_bool(self.block_public_acls),
"ignorePublicAcls": convert_str_to_bool(self.ignore_public_acls),
"blockPublicPolicy": convert_str_to_bool(self.block_public_policy),
"restrictPublicBuckets": convert_str_to_bool(self.restrict_public_buckets),
}
class FakeBucket(BaseModel):
def __init__(self, name, region_name):
self.name = name
@ -746,6 +777,7 @@ class FakeBucket(BaseModel):
self.accelerate_configuration = None
self.payer = "BucketOwner"
self.creation_date = datetime.datetime.utcnow()
self.public_access_block = None
@property
def location(self):
@ -1079,13 +1111,16 @@ class FakeBucket(BaseModel):
}
# Make the supplementary configuration:
# TODO: Implement Public Access Block Support
# This is a dobule-wrapped JSON for some reason...
s_config = {
"AccessControlList": json.dumps(json.dumps(self.acl.to_config_dict()))
}
if self.public_access_block:
s_config["PublicAccessBlockConfiguration"] = json.dumps(
self.public_access_block.to_config_dict()
)
# Tagging is special:
if config_dict["tags"]:
s_config["BucketTaggingConfiguration"] = json.dumps(
@ -1221,6 +1256,14 @@ class S3Backend(BaseBackend):
bucket = self.get_bucket(bucket_name)
return bucket.website_configuration
def get_bucket_public_access_block(self, bucket_name):
bucket = self.get_bucket(bucket_name)
if not bucket.public_access_block:
raise NoSuchPublicAccessBlockConfiguration()
return bucket.public_access_block
def set_key(
self, bucket_name, key_name, value, storage=None, etag=None, multipart=None
):
@ -1309,6 +1352,10 @@ class S3Backend(BaseBackend):
bucket = self.get_bucket(bucket_name)
bucket.delete_cors()
def delete_bucket_public_access_block(self, bucket_name):
bucket = self.get_bucket(bucket_name)
bucket.public_access_block = None
def put_bucket_notification_configuration(self, bucket_name, notification_config):
bucket = self.get_bucket(bucket_name)
bucket.set_notification_configuration(notification_config)
@ -1324,6 +1371,19 @@ class S3Backend(BaseBackend):
raise InvalidRequest("PutBucketAccelerateConfiguration")
bucket.set_accelerate_configuration(accelerate_configuration)
def put_bucket_public_access_block(self, bucket_name, pub_block_config):
bucket = self.get_bucket(bucket_name)
if not pub_block_config:
raise InvalidPublicAccessBlockConfiguration()
bucket.public_access_block = PublicAccessBlock(
pub_block_config.get("BlockPublicAcls"),
pub_block_config.get("IgnorePublicAcls"),
pub_block_config.get("BlockPublicPolicy"),
pub_block_config.get("RestrictPublicBuckets"),
)
def initiate_multipart(self, bucket_name, key_name, metadata):
bucket = self.get_bucket(bucket_name)
new_multipart = FakeMultipart(key_name, metadata)

View File

@ -1,10 +1,11 @@
from __future__ import unicode_literals
import re
import sys
import six
from moto.core.utils import str_to_rfc_1123_datetime
from moto.core.utils import str_to_rfc_1123_datetime, py2_strip_unicode_keys
from six.moves.urllib.parse import parse_qs, urlparse, unquote
import xmltodict
@ -12,6 +13,7 @@ import xmltodict
from moto.packages.httpretty.core import HTTPrettyRequest
from moto.core.responses import _TemplateEnvironmentMixin, ActionAuthenticatorMixin
from moto.core.utils import path_url
from moto.core import ACCOUNT_ID
from moto.s3bucket_path.utils import (
bucket_name_from_url as bucketpath_bucket_name_from_url,
@ -70,6 +72,7 @@ ACTION_MAP = {
"notification": "GetBucketNotification",
"accelerate": "GetAccelerateConfiguration",
"versions": "ListBucketVersions",
"public_access_block": "GetPublicAccessBlock",
"DEFAULT": "ListBucket",
},
"PUT": {
@ -83,6 +86,7 @@ ACTION_MAP = {
"cors": "PutBucketCORS",
"notification": "PutBucketNotification",
"accelerate": "PutAccelerateConfiguration",
"public_access_block": "PutPublicAccessBlock",
"DEFAULT": "CreateBucket",
},
"DELETE": {
@ -90,6 +94,7 @@ ACTION_MAP = {
"policy": "DeleteBucketPolicy",
"tagging": "PutBucketTagging",
"cors": "PutBucketCORS",
"public_access_block": "DeletePublicAccessBlock",
"DEFAULT": "DeleteBucket",
},
},
@ -399,6 +404,12 @@ class ResponseObject(_TemplateEnvironmentMixin, ActionAuthenticatorMixin):
return 200, {}, template.render()
template = self.response_template(S3_BUCKET_ACCELERATE)
return template.render(bucket=bucket)
elif "publicAccessBlock" in querystring:
public_block_config = self.backend.get_bucket_public_access_block(
bucket_name
)
template = self.response_template(S3_PUBLIC_ACCESS_BLOCK_CONFIGURATION)
return template.render(public_block_config=public_block_config)
elif "versions" in querystring:
delimiter = querystring.get("delimiter", [None])[0]
@ -651,6 +662,23 @@ class ResponseObject(_TemplateEnvironmentMixin, ActionAuthenticatorMixin):
except Exception as e:
raise e
elif "publicAccessBlock" in querystring:
parsed_xml = xmltodict.parse(body)
parsed_xml["PublicAccessBlockConfiguration"].pop("@xmlns", None)
# If Python 2, fix the unicode strings:
if sys.version_info[0] < 3:
parsed_xml = {
"PublicAccessBlockConfiguration": py2_strip_unicode_keys(
dict(parsed_xml["PublicAccessBlockConfiguration"])
)
}
self.backend.put_bucket_public_access_block(
bucket_name, parsed_xml["PublicAccessBlockConfiguration"]
)
return ""
else:
if body:
# us-east-1, the default AWS region behaves a bit differently
@ -706,6 +734,9 @@ class ResponseObject(_TemplateEnvironmentMixin, ActionAuthenticatorMixin):
bucket = self.backend.get_bucket(bucket_name)
bucket.delete_lifecycle()
return 204, {}, ""
elif "publicAccessBlock" in querystring:
self.backend.delete_bucket_public_access_block(bucket_name)
return 204, {}, ""
removed_bucket = self.backend.delete_bucket(bucket_name)
@ -1460,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>
@ -1492,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 %}
@ -1653,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>
@ -1856,7 +1893,8 @@ S3_MULTIPART_COMPLETE_RESPONSE = """<?xml version="1.0" encoding="UTF-8"?>
</CompleteMultipartUploadResult>
"""
S3_ALL_MULTIPARTS = """<?xml version="1.0" encoding="UTF-8"?>
S3_ALL_MULTIPARTS = (
"""<?xml version="1.0" encoding="UTF-8"?>
<ListMultipartUploadsResult xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
<Bucket>{{ bucket_name }}</Bucket>
<KeyMarker></KeyMarker>
@ -1868,7 +1906,9 @@ S3_ALL_MULTIPARTS = """<?xml version="1.0" encoding="UTF-8"?>
<Key>{{ upload.key_name }}</Key>
<UploadId>{{ upload.id }}</UploadId>
<Initiator>
<ID>arn:aws:iam::123456789012:user/user1-11111a31-17b5-4fb7-9df5-b111111f13de</ID>
<ID>arn:aws:iam::"""
+ ACCOUNT_ID
+ """:user/user1-11111a31-17b5-4fb7-9df5-b111111f13de</ID>
<DisplayName>user1-11111a31-17b5-4fb7-9df5-b111111f13de</DisplayName>
</Initiator>
<Owner>
@ -1881,6 +1921,7 @@ S3_ALL_MULTIPARTS = """<?xml version="1.0" encoding="UTF-8"?>
{% endfor %}
</ListMultipartUploadsResult>
"""
)
S3_NO_POLICY = """<?xml version="1.0" encoding="UTF-8"?>
<Error>
@ -2053,3 +2094,12 @@ S3_BUCKET_ACCELERATE = """
S3_BUCKET_ACCELERATE_NOT_SET = """
<AccelerateConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/"/>
"""
S3_PUBLIC_ACCESS_BLOCK_CONFIGURATION = """
<PublicAccessBlockConfiguration>
<BlockPublicAcls>{{public_block_config.block_public_acls}}</BlockPublicAcls>
<IgnorePublicAcls>{{public_block_config.ignore_public_acls}}</IgnorePublicAcls>
<BlockPublicPolicy>{{public_block_config.block_public_policy}}</BlockPublicPolicy>
<RestrictPublicBuckets>{{public_block_config.restrict_public_buckets}}</RestrictPublicBuckets>
</PublicAccessBlockConfiguration>
"""

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

@ -1,3 +1,5 @@
from moto.core import ACCOUNT_ID
"""
SES Feedback messages
Extracted from https://docs.aws.amazon.com/ses/latest/DeveloperGuide/notification-contents.html
@ -10,7 +12,7 @@ COMMON_MAIL = {
"source": "sender@example.com",
"sourceArn": "arn:aws:ses:us-west-2:888888888888:identity/example.com",
"sourceIp": "127.0.3.0",
"sendingAccountId": "123456789012",
"sendingAccountId": ACCOUNT_ID,
"destination": ["recipient@example.com"],
"headersTruncated": False,
"headers": [

View File

@ -31,7 +31,8 @@ from .exceptions import (
)
from .utils import make_arn_for_topic, make_arn_for_subscription, is_e164
DEFAULT_ACCOUNT_ID = 123456789012
from moto.core import ACCOUNT_ID as DEFAULT_ACCOUNT_ID
DEFAULT_PAGE_SIZE = 100
MAXIMUM_MESSAGE_LENGTH = 262144 # 256 KiB
@ -259,7 +260,9 @@ class Subscription(BaseModel):
"SignatureVersion": "1",
"Signature": "EXAMPLElDMXvB8r9R83tGoNn0ecwd5UjllzsvSvbItzfaMpN2nk5HVSw7XnOn/49IkxDKz8YrlH2qJXj2iZB0Zo2O71c4qQk1fMUDi3LGpij7RCW7AW9vYYsSqIKRnFS94ilu7NFhUzLiieYr4BKHpdTmdD6c0esKEYBpabxDSc=",
"SigningCertURL": "https://sns.us-east-1.amazonaws.com/SimpleNotificationService-f3ecfb7224c7233fe7bb5f59f96de52f.pem",
"UnsubscribeURL": "https://sns.us-east-1.amazonaws.com/?Action=Unsubscribe&SubscriptionArn=arn:aws:sns:us-east-1:123456789012:some-topic:2bcfbf39-05c3-41de-beaa-fcfcc21c8f55",
"UnsubscribeURL": "https://sns.us-east-1.amazonaws.com/?Action=Unsubscribe&SubscriptionArn=arn:aws:sns:us-east-1:{}:some-topic:2bcfbf39-05c3-41de-beaa-fcfcc21c8f55".format(
DEFAULT_ACCOUNT_ID
),
}
if message_attributes:
post_data["MessageAttributes"] = message_attributes
@ -275,8 +278,11 @@ class PlatformApplication(BaseModel):
@property
def arn(self):
return "arn:aws:sns:{region}:123456789012:app/{platform}/{name}".format(
region=self.region, platform=self.platform, name=self.name
return "arn:aws:sns:{region}:{AccountId}:app/{platform}/{name}".format(
region=self.region,
platform=self.platform,
name=self.name,
AccountId=DEFAULT_ACCOUNT_ID,
)
@ -305,8 +311,9 @@ class PlatformEndpoint(BaseModel):
@property
def arn(self):
return "arn:aws:sns:{region}:123456789012:endpoint/{platform}/{name}/{id}".format(
return "arn:aws:sns:{region}:{AccountId}:endpoint/{platform}/{name}/{id}".format(
region=self.region,
AccountId=DEFAULT_ACCOUNT_ID,
platform=self.application.platform,
name=self.application.name,
id=self.id,
@ -390,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):
@ -432,11 +435,18 @@ class SNSBackend(BaseBackend):
subscription = Subscription(topic, endpoint, protocol)
attributes = {
"PendingConfirmation": "false",
"ConfirmationWasAuthenticated": "true",
"Endpoint": endpoint,
"TopicArn": topic_arn,
"Protocol": protocol,
"SubscriptionArn": subscription.arn,
"Owner": DEFAULT_ACCOUNT_ID,
"RawMessageDelivery": "false",
}
if protocol in ["http", "https"]:
attributes["EffectiveDeliveryPolicy"] = topic.effective_delivery_policy
subscription.attributes = attributes
self.subscriptions[subscription.arn] = subscription
return subscription
@ -452,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:
@ -693,11 +703,13 @@ 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 = {
"http": {
"disableSubscriptionOverrides": False,
"defaultHealthyRetryPolicy": {
"numNoDelayRetries": 0,
"numMinDelayRetries": 0,
@ -707,7 +719,9 @@ DEFAULT_EFFECTIVE_DELIVERY_POLICY = {
"numRetries": 3,
"backoffFunction": "linear",
},
}
"sicklyRetryPolicy": None,
"throttlePolicy": None,
"guaranteed": False,
}

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
@ -32,7 +32,8 @@ from .exceptions import (
InvalidAttributeName,
)
DEFAULT_ACCOUNT_ID = 123456789012
from moto.core import ACCOUNT_ID as DEFAULT_ACCOUNT_ID
DEFAULT_SENDER_ID = "AIDAIT2UOQQY3AUEKVGXU"
MAXIMUM_MESSAGE_LENGTH = 262144 # 256 KiB
@ -417,8 +418,8 @@ class Queue(BaseModel):
return result
def url(self, request_url):
return "{0}://{1}/123456789012/{2}".format(
request_url.scheme, request_url.netloc, self.name
return "{0}://{1}/{2}/{3}".format(
request_url.scheme, request_url.netloc, DEFAULT_ACCOUNT_ID, self.name
)
@property
@ -856,5 +857,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

@ -13,6 +13,7 @@ import time
import uuid
import itertools
from .utils import parameter_arn
from .exceptions import (
ValidationException,
InvalidFilterValue,
@ -60,19 +61,23 @@ class Parameter(BaseModel):
if value.startswith(prefix):
return value[len(prefix) :]
def response_object(self, decrypt=False):
def response_object(self, decrypt=False, region=None):
r = {
"Name": self.name,
"Type": self.type,
"Value": self.decrypt(self.value) if decrypt else self.value,
"Version": self.version,
"LastModifiedDate": round(self.last_modified_date, 3),
}
if region:
r["ARN"] = parameter_arn(region, self.name)
return r
def describe_response_object(self, decrypt=False):
r = self.response_object(decrypt)
r["LastModifiedDate"] = int(self.last_modified_date)
r["LastModifiedDate"] = round(self.last_modified_date, 3)
r["LastModifiedUser"] = "N/A"
if self.description:
@ -510,7 +515,15 @@ class SimpleSystemManagerBackend(BaseBackend):
result.append(self.get_parameter(name, with_decryption))
return result
def get_parameters_by_path(self, path, with_decryption, recursive, filters=None):
def get_parameters_by_path(
self,
path,
with_decryption,
recursive,
filters=None,
next_token=None,
max_results=10,
):
"""Implement the get-parameters-by-path-API in the backend."""
result = []
# path could be with or without a trailing /. we handle this
@ -527,7 +540,19 @@ class SimpleSystemManagerBackend(BaseBackend):
continue
result.append(self.get_parameter(param_name, with_decryption))
return result
return self._get_values_nexttoken(result, max_results, next_token)
def _get_values_nexttoken(self, values_list, max_results, next_token=None):
if next_token is None:
next_token = 0
next_token = int(next_token)
max_results = int(max_results)
values = values_list[next_token : next_token + max_results]
if len(values) == max_results:
next_token = str(next_token + max_results)
else:
next_token = None
return values, next_token
def get_parameter_history(self, name, with_decryption):
if name in self._parameters:

View File

@ -51,7 +51,7 @@ class SimpleSystemManagerResponse(BaseResponse):
}
return json.dumps(error), dict(status=400)
response = {"Parameter": result.response_object(with_decryption)}
response = {"Parameter": result.response_object(with_decryption, self.region)}
return json.dumps(response)
def get_parameters(self):
@ -63,7 +63,7 @@ class SimpleSystemManagerResponse(BaseResponse):
response = {"Parameters": [], "InvalidParameters": []}
for parameter in result:
param_data = parameter.response_object(with_decryption)
param_data = parameter.response_object(with_decryption, self.region)
response["Parameters"].append(param_data)
param_names = [param.name for param in result]
@ -77,15 +77,22 @@ class SimpleSystemManagerResponse(BaseResponse):
with_decryption = self._get_param("WithDecryption")
recursive = self._get_param("Recursive", False)
filters = self._get_param("ParameterFilters")
token = self._get_param("NextToken")
max_results = self._get_param("MaxResults", 10)
result = self.ssm_backend.get_parameters_by_path(
path, with_decryption, recursive, filters
result, next_token = self.ssm_backend.get_parameters_by_path(
path,
with_decryption,
recursive,
filters,
next_token=token,
max_results=max_results,
)
response = {"Parameters": []}
response = {"Parameters": [], "NextToken": next_token}
for parameter in result:
param_data = parameter.response_object(with_decryption)
param_data = parameter.response_object(with_decryption, self.region)
response["Parameters"].append(param_data)
return json.dumps(response)

9
moto/ssm/utils.py Normal file
View File

@ -0,0 +1,9 @@
ACCOUNT_ID = "1234567890"
def parameter_arn(region, parameter_name):
if parameter_name[0] == "/":
parameter_name = parameter_name[1:]
return "arn:aws:ssm:{0}:{1}:parameter/{2}".format(
region, ACCOUNT_ID, parameter_name
)

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

@ -2,7 +2,7 @@ from __future__ import unicode_literals
import datetime
from moto.core import BaseBackend, BaseModel
from moto.core.utils import iso_8601_datetime_with_milliseconds
from moto.iam.models import ACCOUNT_ID
from moto.core import ACCOUNT_ID
from moto.sts.utils import (
random_access_key_id,
random_secret_access_key,

View File

@ -1,7 +1,7 @@
from __future__ import unicode_literals
from moto.core.responses import BaseResponse
from moto.iam.models import ACCOUNT_ID
from moto.core import ACCOUNT_ID
from moto.iam import iam_backend
from .exceptions import STSValidationError
from .models import sts_backend

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

@ -2,8 +2,9 @@
mock
nose
black; python_version >= '3.6'
regex==2019.11.1; python_version >= '3.6' # Needed for black
sure==1.4.11
coverage
coverage==4.5.4
flake8==3.7.8
freezegun
flask

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

@ -9,6 +9,7 @@ import uuid
from botocore.exceptions import ClientError
from moto import mock_acm
from moto.core import ACCOUNT_ID
RESOURCE_FOLDER = os.path.join(os.path.dirname(__file__), "resources")
@ -19,7 +20,9 @@ SERVER_CRT = _GET_RESOURCE("star_moto_com.pem")
SERVER_COMMON_NAME = "*.moto.com"
SERVER_CRT_BAD = _GET_RESOURCE("star_moto_com-bad.pem")
SERVER_KEY = _GET_RESOURCE("star_moto_com.key")
BAD_ARN = "arn:aws:acm:us-east-2:123456789012:certificate/_0000000-0000-0000-0000-000000000000"
BAD_ARN = "arn:aws:acm:us-east-2:{}:certificate/_0000000-0000-0000-0000-000000000000".format(
ACCOUNT_ID
)
def _import_cert(client):

View File

@ -9,6 +9,7 @@ from botocore.exceptions import ClientError
import responses
from moto import mock_apigateway, settings
from moto.core import ACCOUNT_ID
from nose.tools import assert_raises
@ -881,7 +882,9 @@ def test_put_integration_validation():
client.put_integration(
restApiId=api_id,
resourceId=root_id,
credentials="arn:aws:iam::123456789012:role/service-role/testfunction-role-oe783psq",
credentials="arn:aws:iam::{}:role/service-role/testfunction-role-oe783psq".format(
ACCOUNT_ID
),
httpMethod="GET",
type=type,
uri="arn:aws:apigateway:us-west-2:s3:path/b/k",
@ -903,7 +906,9 @@ def test_put_integration_validation():
client.put_integration(
restApiId=api_id,
resourceId=root_id,
credentials="arn:aws:iam::123456789012:role/service-role/testfunction-role-oe783psq",
credentials="arn:aws:iam::{}:role/service-role/testfunction-role-oe783psq".format(
ACCOUNT_ID
),
httpMethod="GET",
type=type,
uri="arn:aws:apigateway:us-west-2:s3:path/b/k",
@ -1130,6 +1135,23 @@ def test_http_proxying_integration():
requests.get(deploy_url).content.should.equal(b"a fake response")
@mock_apigateway
def test_create_api_key():
region_name = "us-west-2"
client = boto3.client("apigateway", region_name=region_name)
apikey_value = "12345"
apikey_name = "TESTKEY1"
payload = {"value": apikey_value, "name": apikey_name}
client.create_api_key(**payload)
response = client.get_api_keys()
len(response["items"]).should.equal(1)
client.create_api_key.when.called_with(**payload).should.throw(ClientError)
@mock_apigateway
def test_api_keys():
region_name = "us-west-2"
@ -1139,26 +1161,18 @@ def test_api_keys():
apikey_value = "12345"
apikey_name = "TESTKEY1"
payload = {"value": apikey_value, "name": apikey_name}
payload = {
"value": apikey_value,
"name": apikey_name,
"tags": {"tag1": "test_tag1", "tag2": "1"},
}
response = client.create_api_key(**payload)
apikey_id = response["id"]
apikey = client.get_api_key(apiKey=response["id"])
apikey["name"].should.equal(apikey_name)
apikey["value"].should.equal(apikey_value)
apikey_name = "TESTKEY2"
payload = {"name": apikey_name, "tags": {"tag1": "test_tag1", "tag2": "1"}}
response = client.create_api_key(**payload)
apikey_id = response["id"]
apikey = client.get_api_key(apiKey=apikey_id)
apikey["name"].should.equal(apikey_name)
apikey["tags"]["tag1"].should.equal("test_tag1")
apikey["tags"]["tag2"].should.equal("1")
len(apikey["value"]).should.equal(40)
apikey_name = "TESTKEY3"
payload = {"name": apikey_name}
response = client.create_api_key(**payload)
apikey_id = response["id"]
patch_operations = [
{"op": "replace", "path": "/name", "value": "TESTKEY3_CHANGE"},
@ -1172,13 +1186,25 @@ def test_api_keys():
response["description"].should.equal("APIKEY UPDATE TEST")
response["enabled"].should.equal(False)
updated_api_key = client.get_api_key(apiKey=apikey_id)
updated_api_key["name"].should.equal("TESTKEY3_CHANGE")
updated_api_key["customerId"].should.equal("12345")
updated_api_key["description"].should.equal("APIKEY UPDATE TEST")
updated_api_key["enabled"].should.equal(False)
response = client.get_api_keys()
len(response["items"]).should.equal(3)
len(response["items"]).should.equal(1)
payload = {"name": apikey_name}
client.create_api_key(**payload)
response = client.get_api_keys()
len(response["items"]).should.equal(2)
client.delete_api_key(apiKey=apikey_id)
response = client.get_api_keys()
len(response["items"]).should.equal(2)
len(response["items"]).should.equal(1)
@mock_apigateway

View File

@ -8,6 +8,7 @@ import sure # noqa
from moto import mock_autoscaling_deprecated
from moto import mock_autoscaling
from moto.core import ACCOUNT_ID
from tests.helpers import requires_boto_gte
@ -22,7 +23,9 @@ def test_create_launch_configuration():
security_groups=["default", "default2"],
user_data=b"This is some user_data",
instance_monitoring=True,
instance_profile_name="arn:aws:iam::123456789012:instance-profile/testing",
instance_profile_name="arn:aws:iam::{}:instance-profile/testing".format(
ACCOUNT_ID
),
spot_price=0.1,
)
conn.create_launch_configuration(config)
@ -36,7 +39,7 @@ def test_create_launch_configuration():
launch_config.user_data.should.equal(b"This is some user_data")
launch_config.instance_monitoring.enabled.should.equal("true")
launch_config.instance_profile_name.should.equal(
"arn:aws:iam::123456789012:instance-profile/testing"
"arn:aws:iam::{}:instance-profile/testing".format(ACCOUNT_ID)
)
launch_config.spot_price.should.equal(0.1)
@ -71,7 +74,9 @@ def test_create_launch_configuration_with_block_device_mappings():
security_groups=["default", "default2"],
user_data=b"This is some user_data",
instance_monitoring=True,
instance_profile_name="arn:aws:iam::123456789012:instance-profile/testing",
instance_profile_name="arn:aws:iam::{}:instance-profile/testing".format(
ACCOUNT_ID
),
spot_price=0.1,
block_device_mappings=[block_device_mapping],
)
@ -86,7 +91,7 @@ def test_create_launch_configuration_with_block_device_mappings():
launch_config.user_data.should.equal(b"This is some user_data")
launch_config.instance_monitoring.enabled.should.equal("true")
launch_config.instance_profile_name.should.equal(
"arn:aws:iam::123456789012:instance-profile/testing"
"arn:aws:iam::{}:instance-profile/testing".format(ACCOUNT_ID)
)
launch_config.spot_price.should.equal(0.1)
len(launch_config.block_device_mappings).should.equal(3)

View File

@ -306,8 +306,8 @@ def test_create_function_from_aws_bucket():
result.should.equal(
{
"FunctionName": "testFunction",
"FunctionArn": "arn:aws:lambda:{}:123456789012:function:testFunction".format(
_lambda_region
"FunctionArn": "arn:aws:lambda:{}:{}:function:testFunction".format(
_lambda_region, ACCOUNT_ID
),
"Runtime": "python2.7",
"Role": result["Role"],
@ -353,8 +353,8 @@ def test_create_function_from_zipfile():
result.should.equal(
{
"FunctionName": "testFunction",
"FunctionArn": "arn:aws:lambda:{}:123456789012:function:testFunction".format(
_lambda_region
"FunctionArn": "arn:aws:lambda:{}:{}:function:testFunction".format(
_lambda_region, ACCOUNT_ID
),
"Runtime": "python2.7",
"Role": result["Role"],
@ -431,7 +431,7 @@ def test_get_function():
result = conn.get_function(FunctionName="testFunction", Qualifier="$LATEST")
result["Configuration"]["Version"].should.equal("$LATEST")
result["Configuration"]["FunctionArn"].should.equal(
"arn:aws:lambda:us-west-2:123456789012:function:testFunction:$LATEST"
"arn:aws:lambda:us-west-2:{}:function:testFunction:$LATEST".format(ACCOUNT_ID)
)
# Test get function when can't find function name
@ -620,8 +620,8 @@ def test_list_create_list_get_delete_list():
"CodeSha256": hashlib.sha256(zip_content).hexdigest(),
"CodeSize": len(zip_content),
"Description": "test lambda function",
"FunctionArn": "arn:aws:lambda:{}:123456789012:function:testFunction".format(
_lambda_region
"FunctionArn": "arn:aws:lambda:{}:{}:function:testFunction".format(
_lambda_region, ACCOUNT_ID
),
"FunctionName": "testFunction",
"Handler": "lambda_function.lambda_handler",
@ -749,16 +749,17 @@ def test_tags_not_found():
"""
conn = boto3.client("lambda", "us-west-2")
conn.list_tags.when.called_with(
Resource="arn:aws:lambda:123456789012:function:not-found"
Resource="arn:aws:lambda:{}:function:not-found".format(ACCOUNT_ID)
).should.throw(botocore.client.ClientError)
conn.tag_resource.when.called_with(
Resource="arn:aws:lambda:123456789012:function:not-found",
Resource="arn:aws:lambda:{}:function:not-found".format(ACCOUNT_ID),
Tags=dict(spam="eggs"),
).should.throw(botocore.client.ClientError)
conn.untag_resource.when.called_with(
Resource="arn:aws:lambda:123456789012:function:not-found", TagKeys=["spam"]
Resource="arn:aws:lambda:{}:function:not-found".format(ACCOUNT_ID),
TagKeys=["spam"],
).should.throw(botocore.client.ClientError)
@ -815,8 +816,8 @@ def test_get_function_created_with_zipfile():
"CodeSha256": hashlib.sha256(zip_content).hexdigest(),
"CodeSize": len(zip_content),
"Description": "test lambda function",
"FunctionArn": "arn:aws:lambda:{}:123456789012:function:testFunction".format(
_lambda_region
"FunctionArn": "arn:aws:lambda:{}:{}:function:testFunction".format(
_lambda_region, ACCOUNT_ID
),
"FunctionName": "testFunction",
"Handler": "lambda_function.handler",
@ -921,18 +922,15 @@ def test_list_versions_by_function():
assert res["ResponseMetadata"]["HTTPStatusCode"] == 201
versions = conn.list_versions_by_function(FunctionName="testFunction")
assert len(versions["Versions"]) == 3
assert (
versions["Versions"][0]["FunctionArn"]
== "arn:aws:lambda:us-west-2:123456789012:function:testFunction:$LATEST"
)
assert (
versions["Versions"][1]["FunctionArn"]
== "arn:aws:lambda:us-west-2:123456789012:function:testFunction:1"
)
assert (
versions["Versions"][2]["FunctionArn"]
== "arn:aws:lambda:us-west-2:123456789012:function:testFunction:2"
)
assert versions["Versions"][0][
"FunctionArn"
] == "arn:aws:lambda:us-west-2:{}:function:testFunction:$LATEST".format(ACCOUNT_ID)
assert versions["Versions"][1][
"FunctionArn"
] == "arn:aws:lambda:us-west-2:{}:function:testFunction:1".format(ACCOUNT_ID)
assert versions["Versions"][2][
"FunctionArn"
] == "arn:aws:lambda:us-west-2:{}:function:testFunction:2".format(ACCOUNT_ID)
conn.create_function(
FunctionName="testFunction_2",
@ -947,9 +945,10 @@ def test_list_versions_by_function():
)
versions = conn.list_versions_by_function(FunctionName="testFunction_2")
assert len(versions["Versions"]) == 1
assert (
versions["Versions"][0]["FunctionArn"]
== "arn:aws:lambda:us-west-2:123456789012:function:testFunction_2:$LATEST"
assert versions["Versions"][0][
"FunctionArn"
] == "arn:aws:lambda:us-west-2:{}:function:testFunction_2:$LATEST".format(
ACCOUNT_ID
)
@ -1426,8 +1425,8 @@ def test_update_function_zip():
"CodeSha256": hashlib.sha256(zip_content_two).hexdigest(),
"CodeSize": len(zip_content_two),
"Description": "test lambda function",
"FunctionArn": "arn:aws:lambda:{}:123456789012:function:testFunctionZip:2".format(
_lambda_region
"FunctionArn": "arn:aws:lambda:{}:{}:function:testFunctionZip:2".format(
_lambda_region, ACCOUNT_ID
),
"FunctionName": "testFunctionZip",
"Handler": "lambda_function.lambda_handler",
@ -1488,8 +1487,8 @@ def test_update_function_s3():
"CodeSha256": hashlib.sha256(zip_content_two).hexdigest(),
"CodeSize": len(zip_content_two),
"Description": "test lambda function",
"FunctionArn": "arn:aws:lambda:{}:123456789012:function:testFunctionS3:2".format(
_lambda_region
"FunctionArn": "arn:aws:lambda:{}:{}:function:testFunctionS3:2".format(
_lambda_region, ACCOUNT_ID
),
"FunctionName": "testFunctionS3",
"Handler": "lambda_function.lambda_handler",

View File

@ -55,6 +55,10 @@ def _setup(ec2_client, iam_client):
RoleName="TestRole", AssumeRolePolicyDocument="some_policy"
)
iam_arn = resp["Role"]["Arn"]
iam_client.create_instance_profile(InstanceProfileName="TestRole")
iam_client.add_role_to_instance_profile(
InstanceProfileName="TestRole", RoleName="TestRole"
)
return vpc_id, subnet_id, sg_id, iam_arn
@ -83,7 +87,7 @@ def test_create_managed_compute_environment():
"subnets": [subnet_id],
"securityGroupIds": [sg_id],
"ec2KeyPair": "string",
"instanceRole": iam_arn,
"instanceRole": iam_arn.replace("role", "instance-profile"),
"tags": {"string": "string"},
"bidPercentage": 123,
"spotIamFleetRole": "string",
@ -209,7 +213,7 @@ def test_delete_managed_compute_environment():
"subnets": [subnet_id],
"securityGroupIds": [sg_id],
"ec2KeyPair": "string",
"instanceRole": iam_arn,
"instanceRole": iam_arn.replace("role", "instance-profile"),
"tags": {"string": "string"},
"bidPercentage": 123,
"spotIamFleetRole": "string",
@ -608,6 +612,9 @@ def test_describe_task_definition():
resp = batch_client.describe_job_definitions(jobDefinitions=["sleep10", "test1"])
len(resp["jobDefinitions"]).should.equal(3)
for job_definition in resp["jobDefinitions"]:
job_definition["status"].should.equal("ACTIVE")
@mock_logs
@mock_ec2

View File

@ -51,6 +51,10 @@ def _setup(ec2_client, iam_client):
RoleName="TestRole", AssumeRolePolicyDocument="some_policy"
)
iam_arn = resp["Role"]["Arn"]
iam_client.create_instance_profile(InstanceProfileName="TestRole")
iam_client.add_role_to_instance_profile(
InstanceProfileName="TestRole", RoleName="TestRole"
)
return vpc_id, subnet_id, sg_id, iam_arn
@ -78,7 +82,7 @@ def test_create_env_cf():
"InstanceTypes": ["optimal"],
"Subnets": [subnet_id],
"SecurityGroupIds": [sg_id],
"InstanceRole": iam_arn,
"InstanceRole": iam_arn.replace("role", "instance-profile"),
},
"ServiceRole": iam_arn,
},
@ -129,7 +133,7 @@ def test_create_job_queue_cf():
"InstanceTypes": ["optimal"],
"Subnets": [subnet_id],
"SecurityGroupIds": [sg_id],
"InstanceRole": iam_arn,
"InstanceRole": iam_arn.replace("role", "instance-profile"),
},
"ServiceRole": iam_arn,
},
@ -195,7 +199,7 @@ def test_create_job_def_cf():
"InstanceTypes": ["optimal"],
"Subnets": [subnet_id],
"SecurityGroupIds": [sg_id],
"InstanceRole": iam_arn,
"InstanceRole": iam_arn.replace("role", "instance-profile"),
},
"ServiceRole": iam_arn,
},

View File

@ -14,6 +14,7 @@ import sure # noqa
# Ensure 'assert_raises' context manager support for Python 2.6
import tests.backport_assert_raises # noqa
from nose.tools import assert_raises
from moto.core import ACCOUNT_ID
from moto import (
mock_cloudformation_deprecated,
@ -129,12 +130,12 @@ def test_create_stack_with_notification_arn():
conn.create_stack(
"test_stack_with_notifications",
template_body=dummy_template_json,
notification_arns="arn:aws:sns:us-east-1:123456789012:fake-queue",
notification_arns="arn:aws:sns:us-east-1:{}:fake-queue".format(ACCOUNT_ID),
)
stack = conn.describe_stacks()[0]
[n.value for n in stack.notification_arns].should.contain(
"arn:aws:sns:us-east-1:123456789012:fake-queue"
"arn:aws:sns:us-east-1:{}:fake-queue".format(ACCOUNT_ID)
)

View File

@ -11,6 +11,7 @@ import sure # noqa
from nose.tools import assert_raises
from moto import mock_cloudformation, mock_s3, mock_sqs, mock_ec2
from moto.core import ACCOUNT_ID
dummy_template = {
"AWSTemplateFormatVersion": "2010-09-09",
@ -174,17 +175,17 @@ def test_boto3_describe_stack_instances():
)
cf_conn.create_stack_instances(
StackSetName="test_stack_set",
Accounts=["123456789012"],
Accounts=[ACCOUNT_ID],
Regions=["us-east-1", "us-west-2"],
)
usw2_instance = cf_conn.describe_stack_instance(
StackSetName="test_stack_set",
StackInstanceAccount="123456789012",
StackInstanceAccount=ACCOUNT_ID,
StackInstanceRegion="us-west-2",
)
use1_instance = cf_conn.describe_stack_instance(
StackSetName="test_stack_set",
StackInstanceAccount="123456789012",
StackInstanceAccount=ACCOUNT_ID,
StackInstanceRegion="us-east-1",
)
@ -192,13 +193,13 @@ def test_boto3_describe_stack_instances():
"us-west-2"
)
usw2_instance["StackInstance"].should.have.key("Account").which.should.equal(
"123456789012"
ACCOUNT_ID
)
use1_instance["StackInstance"].should.have.key("Region").which.should.equal(
"us-east-1"
)
use1_instance["StackInstance"].should.have.key("Account").which.should.equal(
"123456789012"
ACCOUNT_ID
)
@ -236,7 +237,7 @@ def test_boto3_stop_stack_set_operation():
)
cf_conn.create_stack_instances(
StackSetName="test_stack_set",
Accounts=["123456789012"],
Accounts=[ACCOUNT_ID],
Regions=["us-east-1", "us-west-1", "us-west-2"],
)
operation_id = cf_conn.list_stack_set_operations(StackSetName="test_stack_set")[
@ -257,7 +258,7 @@ def test_boto3_describe_stack_set_operation():
)
cf_conn.create_stack_instances(
StackSetName="test_stack_set",
Accounts=["123456789012"],
Accounts=[ACCOUNT_ID],
Regions=["us-east-1", "us-west-1", "us-west-2"],
)
operation_id = cf_conn.list_stack_set_operations(StackSetName="test_stack_set")[
@ -282,7 +283,7 @@ def test_boto3_list_stack_set_operation_results():
)
cf_conn.create_stack_instances(
StackSetName="test_stack_set",
Accounts=["123456789012"],
Accounts=[ACCOUNT_ID],
Regions=["us-east-1", "us-west-1", "us-west-2"],
)
operation_id = cf_conn.list_stack_set_operations(StackSetName="test_stack_set")[
@ -297,9 +298,7 @@ def test_boto3_list_stack_set_operation_results():
)
response["Summaries"].should.have.length_of(3)
response["Summaries"][0].should.have.key("Account").which.should.equal(
"123456789012"
)
response["Summaries"][0].should.have.key("Account").which.should.equal(ACCOUNT_ID)
response["Summaries"][1].should.have.key("Status").which.should.equal("STOPPED")
@ -321,28 +320,28 @@ def test_boto3_update_stack_instances():
)
cf_conn.create_stack_instances(
StackSetName="test_stack_set",
Accounts=["123456789012"],
Accounts=[ACCOUNT_ID],
Regions=["us-east-1", "us-west-1", "us-west-2"],
)
cf_conn.update_stack_instances(
StackSetName="test_stack_set",
Accounts=["123456789012"],
Accounts=[ACCOUNT_ID],
Regions=["us-west-1", "us-west-2"],
ParameterOverrides=param_overrides,
)
usw2_instance = cf_conn.describe_stack_instance(
StackSetName="test_stack_set",
StackInstanceAccount="123456789012",
StackInstanceAccount=ACCOUNT_ID,
StackInstanceRegion="us-west-2",
)
usw1_instance = cf_conn.describe_stack_instance(
StackSetName="test_stack_set",
StackInstanceAccount="123456789012",
StackInstanceAccount=ACCOUNT_ID,
StackInstanceRegion="us-west-1",
)
use1_instance = cf_conn.describe_stack_instance(
StackSetName="test_stack_set",
StackInstanceAccount="123456789012",
StackInstanceAccount=ACCOUNT_ID,
StackInstanceRegion="us-east-1",
)
@ -383,13 +382,13 @@ def test_boto3_delete_stack_instances():
)
cf_conn.create_stack_instances(
StackSetName="test_stack_set",
Accounts=["123456789012"],
Accounts=[ACCOUNT_ID],
Regions=["us-east-1", "us-west-2"],
)
cf_conn.delete_stack_instances(
StackSetName="test_stack_set",
Accounts=["123456789012"],
Accounts=[ACCOUNT_ID],
Regions=["us-east-1"],
RetainStacks=False,
)
@ -410,7 +409,7 @@ def test_boto3_create_stack_instances():
)
cf_conn.create_stack_instances(
StackSetName="test_stack_set",
Accounts=["123456789012"],
Accounts=[ACCOUNT_ID],
Regions=["us-east-1", "us-west-2"],
)
@ -419,7 +418,7 @@ def test_boto3_create_stack_instances():
].should.have.length_of(2)
cf_conn.list_stack_instances(StackSetName="test_stack_set")["Summaries"][0][
"Account"
].should.equal("123456789012")
].should.equal(ACCOUNT_ID)
@mock_cloudformation
@ -440,13 +439,13 @@ def test_boto3_create_stack_instances_with_param_overrides():
)
cf_conn.create_stack_instances(
StackSetName="test_stack_set",
Accounts=["123456789012"],
Accounts=[ACCOUNT_ID],
Regions=["us-east-1", "us-west-2"],
ParameterOverrides=param_overrides,
)
usw2_instance = cf_conn.describe_stack_instance(
StackSetName="test_stack_set",
StackInstanceAccount="123456789012",
StackInstanceAccount=ACCOUNT_ID,
StackInstanceRegion="us-west-2",
)
@ -509,12 +508,12 @@ def test_boto3_list_stack_set_operations():
)
cf_conn.create_stack_instances(
StackSetName="test_stack_set",
Accounts=["123456789012"],
Accounts=[ACCOUNT_ID],
Regions=["us-east-1", "us-west-2"],
)
cf_conn.update_stack_instances(
StackSetName="test_stack_set",
Accounts=["123456789012"],
Accounts=[ACCOUNT_ID],
Regions=["us-east-1", "us-west-2"],
)
@ -682,12 +681,12 @@ def test_create_stack_with_notification_arn():
cf.create_stack(
StackName="test_stack_with_notifications",
TemplateBody=dummy_template_json,
NotificationARNs=["arn:aws:sns:us-east-1:123456789012:fake-queue"],
NotificationARNs=["arn:aws:sns:us-east-1:{}:fake-queue".format(ACCOUNT_ID)],
)
stack = list(cf.stacks.all())[0]
stack.notification_arns.should.contain(
"arn:aws:sns:us-east-1:123456789012:fake-queue"
"arn:aws:sns:us-east-1:{}:fake-queue".format(ACCOUNT_ID)
)
@ -697,10 +696,10 @@ def test_create_stack_with_role_arn():
cf.create_stack(
StackName="test_stack_with_notifications",
TemplateBody=dummy_template_json,
RoleARN="arn:aws:iam::123456789012:role/moto",
RoleARN="arn:aws:iam::{}:role/moto".format(ACCOUNT_ID),
)
stack = list(cf.stacks.all())[0]
stack.role_arn.should.equal("arn:aws:iam::123456789012:role/moto")
stack.role_arn.should.equal("arn:aws:iam::{}:role/moto".format(ACCOUNT_ID))
@mock_cloudformation
@ -1019,7 +1018,7 @@ def test_describe_updated_stack():
cf_conn.update_stack(
StackName="test_stack",
RoleARN="arn:aws:iam::123456789012:role/moto",
RoleARN="arn:aws:iam::{}:role/moto".format(ACCOUNT_ID),
TemplateBody=dummy_update_template_json,
Tags=[{"Key": "foo", "Value": "baz"}],
)
@ -1030,7 +1029,7 @@ def test_describe_updated_stack():
stack_by_id["StackId"].should.equal(stack["StackId"])
stack_by_id["StackName"].should.equal("test_stack")
stack_by_id["StackStatus"].should.equal("UPDATE_COMPLETE")
stack_by_id["RoleARN"].should.equal("arn:aws:iam::123456789012:role/moto")
stack_by_id["RoleARN"].should.equal("arn:aws:iam::{}:role/moto".format(ACCOUNT_ID))
stack_by_id["Tags"].should.equal([{"Key": "foo", "Value": "baz"}])

View File

@ -43,6 +43,7 @@ from moto import (
mock_sqs_deprecated,
mock_elbv2,
)
from moto.core import ACCOUNT_ID
from moto.dynamodb2.models import Table
from .fixtures import (
@ -1912,7 +1913,7 @@ def test_stack_spot_fleet():
"Type": "AWS::EC2::SpotFleet",
"Properties": {
"SpotFleetRequestConfigData": {
"IamFleetRole": "arn:aws:iam::123456789012:role/fleet",
"IamFleetRole": "arn:aws:iam::{}:role/fleet".format(ACCOUNT_ID),
"SpotPrice": "0.12",
"TargetCapacity": 6,
"AllocationStrategy": "diversified",
@ -1933,7 +1934,9 @@ def test_stack_spot_fleet():
"SecurityGroups": [{"GroupId": "sg-123"}],
"SubnetId": subnet_id,
"IamInstanceProfile": {
"Arn": "arn:aws:iam::123456789012:role/fleet"
"Arn": "arn:aws:iam::{}:role/fleet".format(
ACCOUNT_ID
)
},
"WeightedCapacity": "4",
"SpotPrice": "10.00",
@ -1966,7 +1969,7 @@ def test_stack_spot_fleet():
spot_fleet_config["SpotPrice"].should.equal("0.12")
spot_fleet_config["TargetCapacity"].should.equal(6)
spot_fleet_config["IamFleetRole"].should.equal(
"arn:aws:iam::123456789012:role/fleet"
"arn:aws:iam::{}:role/fleet".format(ACCOUNT_ID)
)
spot_fleet_config["AllocationStrategy"].should.equal("diversified")
spot_fleet_config["FulfilledCapacity"].should.equal(6.0)
@ -1999,7 +2002,7 @@ def test_stack_spot_fleet_should_figure_out_default_price():
"Type": "AWS::EC2::SpotFleet",
"Properties": {
"SpotFleetRequestConfigData": {
"IamFleetRole": "arn:aws:iam::123456789012:role/fleet",
"IamFleetRole": "arn:aws:iam::{}:role/fleet".format(ACCOUNT_ID),
"TargetCapacity": 6,
"AllocationStrategy": "diversified",
"LaunchSpecifications": [
@ -2018,7 +2021,9 @@ def test_stack_spot_fleet_should_figure_out_default_price():
"SecurityGroups": [{"GroupId": "sg-123"}],
"SubnetId": subnet_id,
"IamInstanceProfile": {
"Arn": "arn:aws:iam::123456789012:role/fleet"
"Arn": "arn:aws:iam::{}:role/fleet".format(
ACCOUNT_ID
)
},
"WeightedCapacity": "4",
},

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

@ -0,0 +1,720 @@
import json
from datetime import datetime
import boto3
import sure # noqa
from botocore.exceptions import ClientError
from nose.tools import assert_raises
from moto import mock_codepipeline, mock_iam
@mock_codepipeline
def test_create_pipeline():
client = boto3.client("codepipeline", region_name="us-east-1")
response = create_basic_codepipeline(client, "test-pipeline")
response["pipeline"].should.equal(
{
"name": "test-pipeline",
"roleArn": "arn:aws:iam::123456789012:role/test-role",
"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",
},
"runOrder": 1,
"configuration": {
"S3Bucket": "test-bucket",
"S3ObjectKey": "test-object",
},
"outputArtifacts": [{"name": "artifact"}],
"inputArtifacts": [],
}
],
},
{
"name": "Stage-2",
"actions": [
{
"name": "Action-1",
"actionTypeId": {
"category": "Approval",
"owner": "AWS",
"provider": "Manual",
"version": "1",
},
"runOrder": 1,
"configuration": {},
"outputArtifacts": [],
"inputArtifacts": [],
}
],
},
],
"version": 1,
}
)
response["tags"].should.equal([{"key": "key", "value": "value"}])
@mock_codepipeline
@mock_iam
def test_create_pipeline_errors():
client = boto3.client("codepipeline", region_name="us-east-1")
client_iam = boto3.client("iam", region_name="us-east-1")
create_basic_codepipeline(client, "test-pipeline")
with assert_raises(ClientError) as e:
create_basic_codepipeline(client, "test-pipeline")
ex = e.exception
ex.operation_name.should.equal("CreatePipeline")
ex.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
ex.response["Error"]["Code"].should.contain("InvalidStructureException")
ex.response["Error"]["Message"].should.equal(
"A pipeline with the name 'test-pipeline' already exists in account '123456789012'"
)
with assert_raises(ClientError) as e:
client.create_pipeline(
pipeline={
"name": "invalid-pipeline",
"roleArn": "arn:aws:iam::123456789012:role/not-existing",
"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",
},
"runOrder": 1,
},
],
},
],
}
)
ex = e.exception
ex.operation_name.should.equal("CreatePipeline")
ex.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
ex.response["Error"]["Code"].should.contain("InvalidStructureException")
ex.response["Error"]["Message"].should.equal(
"CodePipeline is not authorized to perform AssumeRole on role arn:aws:iam::123456789012:role/not-existing"
)
wrong_role_arn = client_iam.create_role(
RoleName="wrong-role",
AssumeRolePolicyDocument=json.dumps(
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {"Service": "s3.amazonaws.com"},
"Action": "sts:AssumeRole",
}
],
}
),
)["Role"]["Arn"]
with assert_raises(ClientError) as e:
client.create_pipeline(
pipeline={
"name": "invalid-pipeline",
"roleArn": wrong_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",
},
"runOrder": 1,
},
],
},
],
}
)
ex = e.exception
ex.operation_name.should.equal("CreatePipeline")
ex.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
ex.response["Error"]["Code"].should.contain("InvalidStructureException")
ex.response["Error"]["Message"].should.equal(
"CodePipeline is not authorized to perform AssumeRole on role arn:aws:iam::123456789012:role/wrong-role"
)
with assert_raises(ClientError) as e:
client.create_pipeline(
pipeline={
"name": "invalid-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",
},
"runOrder": 1,
},
],
},
],
}
)
ex = e.exception
ex.operation_name.should.equal("CreatePipeline")
ex.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
ex.response["Error"]["Code"].should.contain("InvalidStructureException")
ex.response["Error"]["Message"].should.equal(
"Pipeline has only 1 stage(s). There should be a minimum of 2 stages in a pipeline"
)
@mock_codepipeline
def test_get_pipeline():
client = boto3.client("codepipeline", region_name="us-east-1")
create_basic_codepipeline(client, "test-pipeline")
response = client.get_pipeline(name="test-pipeline")
response["pipeline"].should.equal(
{
"name": "test-pipeline",
"roleArn": "arn:aws:iam::123456789012:role/test-role",
"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",
},
"runOrder": 1,
"configuration": {
"S3Bucket": "test-bucket",
"S3ObjectKey": "test-object",
},
"outputArtifacts": [{"name": "artifact"}],
"inputArtifacts": [],
}
],
},
{
"name": "Stage-2",
"actions": [
{
"name": "Action-1",
"actionTypeId": {
"category": "Approval",
"owner": "AWS",
"provider": "Manual",
"version": "1",
},
"runOrder": 1,
"configuration": {},
"outputArtifacts": [],
"inputArtifacts": [],
}
],
},
],
"version": 1,
}
)
response["metadata"]["pipelineArn"].should.equal(
"arn:aws:codepipeline:us-east-1:123456789012:test-pipeline"
)
response["metadata"]["created"].should.be.a(datetime)
response["metadata"]["updated"].should.be.a(datetime)
@mock_codepipeline
def test_get_pipeline_errors():
client = boto3.client("codepipeline", region_name="us-east-1")
with assert_raises(ClientError) as e:
client.get_pipeline(name="not-existing")
ex = e.exception
ex.operation_name.should.equal("GetPipeline")
ex.response["ResponseMetadata"]["HTTPStatusCode"].should.equal(400)
ex.response["Error"]["Code"].should.contain("PipelineNotFoundException")
ex.response["Error"]["Message"].should.equal(
"Account '123456789012' does not have a pipeline with name 'not-existing'"
)
@mock_codepipeline
def test_update_pipeline():
client = boto3.client("codepipeline", region_name="us-east-1")
create_basic_codepipeline(client, "test-pipeline")
response = client.get_pipeline(name="test-pipeline")
created_time = response["metadata"]["created"]
updated_time = response["metadata"]["updated"]
response = client.update_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": "different-bucket",
"S3ObjectKey": "test-object",
},
"outputArtifacts": [{"name": "artifact"},],
},
],
},
{
"name": "Stage-2",
"actions": [
{
"name": "Action-1",
"actionTypeId": {
"category": "Approval",
"owner": "AWS",
"provider": "Manual",
"version": "1",
},
},
],
},
],
}
)
response["pipeline"].should.equal(
{
"name": "test-pipeline",
"roleArn": "arn:aws:iam::123456789012:role/test-role",
"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",
},
"runOrder": 1,
"configuration": {
"S3Bucket": "different-bucket",
"S3ObjectKey": "test-object",
},
"outputArtifacts": [{"name": "artifact"}],
"inputArtifacts": [],
}
],
},
{
"name": "Stage-2",
"actions": [
{
"name": "Action-1",
"actionTypeId": {
"category": "Approval",
"owner": "AWS",
"provider": "Manual",
"version": "1",
},
"runOrder": 1,
"configuration": {},
"outputArtifacts": [],
"inputArtifacts": [],
}
],
},
],
"version": 2,
}
)
metadata = client.get_pipeline(name="test-pipeline")["metadata"]
metadata["created"].should.equal(created_time)
metadata["updated"].should.be.greater_than(updated_time)
@mock_codepipeline
def test_update_pipeline_errors():
client = boto3.client("codepipeline", region_name="us-east-1")
with assert_raises(ClientError) as e:
client.update_pipeline(
pipeline={
"name": "not-existing",
"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",
},
},
],
},
],
}
)
ex = e.exception
ex.operation_name.should.equal("UpdatePipeline")
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_list_pipelines():
client = boto3.client("codepipeline", region_name="us-east-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(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(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)
@mock_codepipeline
def test_delete_pipeline():
client = boto3.client("codepipeline", region_name="us-east-1")
name = "test-pipeline"
create_basic_codepipeline(client, name)
client.list_pipelines()["pipelines"].should.have.length_of(1)
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=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():
client = boto3.client("iam", region_name="us-east-1")
try:
return client.get_role(RoleName="test-role")["Role"]["Arn"]
except ClientError:
return client.create_role(
RoleName="test-role",
AssumeRolePolicyDocument=json.dumps(
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {"Service": "codepipeline.amazonaws.com"},
"Action": "sts:AssumeRole",
}
],
}
),
)["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

@ -6,6 +6,7 @@ from nose.tools import assert_raises
from moto import mock_cognitoidentity
from moto.cognitoidentity.utils import get_random_identity_id
from moto.core import ACCOUNT_ID
@mock_cognitoidentity
@ -17,7 +18,9 @@ def test_create_identity_pool():
AllowUnauthenticatedIdentities=False,
SupportedLoginProviders={"graph.facebook.com": "123456789012345"},
DeveloperProviderName="devname",
OpenIdConnectProviderARNs=["arn:aws:rds:eu-west-2:123456789012:db:mysql-db"],
OpenIdConnectProviderARNs=[
"arn:aws:rds:eu-west-2:{}:db:mysql-db".format(ACCOUNT_ID)
],
CognitoIdentityProviders=[
{
"ProviderName": "testprovider",
@ -25,7 +28,7 @@ def test_create_identity_pool():
"ServerSideTokenCheck": True,
}
],
SamlProviderARNs=["arn:aws:rds:eu-west-2:123456789012:db:mysql-db"],
SamlProviderARNs=["arn:aws:rds:eu-west-2:{}:db:mysql-db".format(ACCOUNT_ID)],
)
assert result["IdentityPoolId"] != ""
@ -39,7 +42,9 @@ def test_describe_identity_pool():
AllowUnauthenticatedIdentities=False,
SupportedLoginProviders={"graph.facebook.com": "123456789012345"},
DeveloperProviderName="devname",
OpenIdConnectProviderARNs=["arn:aws:rds:eu-west-2:123456789012:db:mysql-db"],
OpenIdConnectProviderARNs=[
"arn:aws:rds:eu-west-2:{}:db:mysql-db".format(ACCOUNT_ID)
],
CognitoIdentityProviders=[
{
"ProviderName": "testprovider",
@ -47,7 +52,7 @@ def test_describe_identity_pool():
"ServerSideTokenCheck": True,
}
],
SamlProviderARNs=["arn:aws:rds:eu-west-2:123456789012:db:mysql-db"],
SamlProviderARNs=["arn:aws:rds:eu-west-2:{}:db:mysql-db".format(ACCOUNT_ID)],
)
result = conn.describe_identity_pool(IdentityPoolId=res["IdentityPoolId"])

View File

@ -14,6 +14,7 @@ from jose import jws
from nose.tools import assert_raises
from moto import mock_cognitoidp
from moto.core import ACCOUNT_ID
@mock_cognitoidp
@ -132,7 +133,9 @@ def test_create_user_pool_domain_custom_domain_config():
domain = str(uuid.uuid4())
custom_domain_config = {
"CertificateArn": "arn:aws:acm:us-east-1:123456789012:certificate/123456789012"
"CertificateArn": "arn:aws:acm:us-east-1:{}:certificate/123456789012".format(
ACCOUNT_ID
)
}
user_pool_id = conn.create_user_pool(PoolName=str(uuid.uuid4()))["UserPool"]["Id"]
result = conn.create_user_pool_domain(
@ -177,7 +180,9 @@ def test_update_user_pool_domain():
domain = str(uuid.uuid4())
custom_domain_config = {
"CertificateArn": "arn:aws:acm:us-east-1:123456789012:certificate/123456789012"
"CertificateArn": "arn:aws:acm:us-east-1:{}:certificate/123456789012".format(
ACCOUNT_ID
)
}
user_pool_id = conn.create_user_pool(PoolName=str(uuid.uuid4()))["UserPool"]["Id"]
conn.create_user_pool_domain(UserPoolId=user_pool_id, Domain=domain)

View File

@ -7,6 +7,7 @@ from nose.tools import assert_raises
from moto import mock_s3
from moto.config import mock_config
from moto.core import ACCOUNT_ID
@mock_config
@ -397,7 +398,9 @@ def test_put_configuration_aggregator():
account_aggregation_source
]
assert (
"arn:aws:config:us-west-2:123456789012:config-aggregator/config-aggregator-"
"arn:aws:config:us-west-2:{}:config-aggregator/config-aggregator-".format(
ACCOUNT_ID
)
in result["ConfigurationAggregator"]["ConfigurationAggregatorArn"]
)
assert (
@ -626,10 +629,10 @@ def test_put_aggregation_authorization():
Tags=[{"Key": "tag", "Value": "a"}],
)
assert (
result["AggregationAuthorization"]["AggregationAuthorizationArn"]
== "arn:aws:config:us-west-2:123456789012:"
"aggregation-authorization/012345678910/us-east-1"
assert result["AggregationAuthorization"][
"AggregationAuthorizationArn"
] == "arn:aws:config:us-west-2:{}:aggregation-authorization/012345678910/us-east-1".format(
ACCOUNT_ID
)
assert result["AggregationAuthorization"]["AuthorizedAccountId"] == "012345678910"
assert result["AggregationAuthorization"]["AuthorizedAwsRegion"] == "us-east-1"
@ -641,10 +644,10 @@ def test_put_aggregation_authorization():
result = client.put_aggregation_authorization(
AuthorizedAccountId="012345678910", AuthorizedAwsRegion="us-east-1"
)
assert (
result["AggregationAuthorization"]["AggregationAuthorizationArn"]
== "arn:aws:config:us-west-2:123456789012:"
"aggregation-authorization/012345678910/us-east-1"
assert result["AggregationAuthorization"][
"AggregationAuthorizationArn"
] == "arn:aws:config:us-west-2:{}:aggregation-authorization/012345678910/us-east-1".format(
ACCOUNT_ID
)
assert result["AggregationAuthorization"]["AuthorizedAccountId"] == "012345678910"
assert result["AggregationAuthorization"]["AuthorizedAwsRegion"] == "us-east-1"
@ -1416,7 +1419,7 @@ def test_list_aggregate_discovered_resource():
assert len(result["ResourceIdentifiers"]) == 12
for x in range(0, 10):
assert result["ResourceIdentifiers"][x] == {
"SourceAccountId": "123456789012",
"SourceAccountId": ACCOUNT_ID,
"ResourceType": "AWS::S3::Bucket",
"ResourceId": "bucket{}".format(x),
"ResourceName": "bucket{}".format(x),
@ -1424,7 +1427,7 @@ def test_list_aggregate_discovered_resource():
}
for x in range(11, 12):
assert result["ResourceIdentifiers"][x] == {
"SourceAccountId": "123456789012",
"SourceAccountId": ACCOUNT_ID,
"ResourceType": "AWS::S3::Bucket",
"ResourceId": "eu-bucket{}".format(x),
"ResourceName": "eu-bucket{}".format(x),

View File

@ -10,7 +10,7 @@ from nose.tools import assert_raises
from moto import mock_iam, mock_ec2, mock_s3, mock_sts, mock_elbv2, mock_rds2
from moto.core import set_initial_no_auth_action_count
from moto.iam.models import ACCOUNT_ID
from moto.core import ACCOUNT_ID
from uuid import uuid4

Some files were not shown because too many files have changed in this diff Show More