diff --git a/.gitignore b/.gitignore index efee854dd..18026d60f 100644 --- a/.gitignore +++ b/.gitignore @@ -11,4 +11,5 @@ build/ .idea/ *.swp .DS_Store -python_env \ No newline at end of file +python_env +.ropeproject/ diff --git a/.travis.yml b/.travis.yml index 4783e13c2..f1b7ac40d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,23 +1,36 @@ language: python sudo: false +services: + - docker python: - 2.7 - 3.6 env: - TEST_SERVER_MODE=false - TEST_SERVER_MODE=true +before_install: + - export BOTO_CONFIG=/dev/null install: - - travis_retry pip install boto==2.45.0 - - travis_retry pip install boto3 - - travis_retry pip install . - - travis_retry pip install -r requirements-dev.txt - - travis_retry pip install coveralls + # We build moto first so the docker container doesn't try to compile it as well, also note we don't use + # -d for docker run so the logs show up in travis + # Python images come from here: https://hub.docker.com/_/python/ - | + python setup.py sdist + if [ "$TEST_SERVER_MODE" = "true" ]; then - AWS_SECRET_ACCESS_KEY=server_secret AWS_ACCESS_KEY_ID=server_key moto_server -p 5000& + docker run --rm -t --name motoserver -e TEST_SERVER_MODE=true -e AWS_SECRET_ACCESS_KEY=server_secret -e AWS_ACCESS_KEY_ID=server_key -v `pwd`:/moto -p 5000:5000 -v /var/run/docker.sock:/var/run/docker.sock python:${TRAVIS_PYTHON_VERSION}-stretch /moto/travis_moto_server.sh & export AWS_SECRET_ACCESS_KEY=foobar_secret export AWS_ACCESS_KEY_ID=foobar_key fi + travis_retry pip install boto==2.45.0 + travis_retry pip install boto3 + travis_retry pip install dist/moto*.gz + travis_retry pip install coveralls==1.1 + travis_retry pip install -r requirements-dev.txt + + if [ "$TEST_SERVER_MODE" = "true" ]; then + python wait_for.py + fi script: - make test after_success: diff --git a/CHANGELOG.md b/CHANGELOG.md index e0ec033f3..109cdcf31 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,121 @@ Moto Changelog Latest ------ +1.1.20 +----- + + * Improved `make scaffold` + * Implemented IAM attached group policies + * Implemented skeleton of Cloudwatch Logs + * Redshift: fixed multi-params + * Redshift: implement taggable resources + * Lambda + SNS: Major enhancements + +1.1.19 +----- + + * Fixing regression from 1.1.15 + +1.1.15 +----- + + * Polly implementation + * Added EC2 instance info + * SNS publish by phone number + +1.1.14 +----- + + * ACM implementation + * Added `make scaffold` + * X-Ray implementation + +1.1.13 +----- + + * Created alpine-based Dockerfile (dockerhub: motoserver/moto) + * SNS.SetSMSAttributes & SNS.GetSMSAttributes + Filtering + * S3 ACL implementation + * pushing to Dockerhub on `make publish` + +1.1.12 +----- + + * implemented all AWS managed policies in source + * fixing Dynamodb CapacityUnits format + * S3 ACL implementation + +1.1.11 +----- + + * S3 authentication + * SSM get_parameter + * ELBv2 target group tagging + * EC2 Security group filters + +1.1.10 +----- + + * EC2 vpc address filtering + * EC2 elastic ip dissociation + * ELBv2 target group tagging + * fixed complexity of accepting new filter implementations + +1.1.9 +----- + + * EC2 root device mapping + +1.1.8 +----- + + * Lambda get_function for function created with zipfile + * scripts/implementation_coverage.py + +1.1.7 +----- + + * Lambda invoke_async + * EC2 keypair filtering + +1.1.6 +----- + + * Dynamo ADD and DELETE operations in update expressions + * Lambda tag support + +1.1.5 +----- + + * Dynamo allow ADD update_item of a string set + * Handle max-keys in list-objects + * bugfixes in pagination + +1.1.3 +----- + + * EC2 vpc_id in responses + +1.1.2 +----- + + * IAM account aliases + * SNS subscription attributes + * bugfixes in Dynamo, CFN, and EC2 + +1.1.1 +----- + + * EC2 group-id filter + * EC2 list support for filters + +1.1.0 +----- + + * Add ELBv2 + * IAM user policies + * RDS snapshots + * IAM policy versions 1.0.1 ----- diff --git a/Dockerfile b/Dockerfile index 72657903e..24d7c34ff 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,11 +1,22 @@ -FROM python:2 +FROM alpine:3.6 + +RUN apk add --no-cache --update \ + gcc \ + musl-dev \ + python3-dev \ + libffi-dev \ + openssl-dev \ + python3 ADD . /moto/ ENV PYTHONUNBUFFERED 1 WORKDIR /moto/ -RUN pip install ".[server]" +RUN python3 -m ensurepip && \ + rm -r /usr/lib/python*/ensurepip && \ + pip3 --no-cache-dir install --upgrade pip setuptools && \ + pip3 --no-cache-dir install ".[server]" -CMD ["moto_server"] +ENTRYPOINT ["/usr/bin/moto_server", "-H", "0.0.0.0"] EXPOSE 5000 diff --git a/MANIFEST.in b/MANIFEST.in index c21ea9947..7e219f463 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,3 +1,4 @@ include README.md LICENSE AUTHORS.md include requirements.txt requirements-dev.txt tox.ini +include moto/ec2/resources/instance_types.json recursive-include tests * diff --git a/Makefile b/Makefile index 300067296..a963c8293 100644 --- a/Makefile +++ b/Makefile @@ -15,5 +15,22 @@ test: lint test_server: @TEST_SERVER_MODE=true nosetests -sv --with-coverage --cover-html ./tests/ -publish: +aws_managed_policies: + scripts/update_managed_policies.py + +upload_pypi_artifact: python setup.py sdist bdist_wheel upload + +push_dockerhub_image: + docker build -t motoserver/moto . + docker push motoserver/moto + +tag_github_release: + git tag `python setup.py --version` + git push origin `python setup.py --version` + +publish: upload_pypi_artifact push_dockerhub_image tag_github_release + +scaffold: + @pip install -r requirements-dev.txt > /dev/null + exec python scripts/scaffold.py diff --git a/README.md b/README.md index cabf6b45f..92ad5d9c0 100644 --- a/README.md +++ b/README.md @@ -58,6 +58,8 @@ It gets even better! Moto isn't just for Python code and it isn't just for S3. L |------------------------------------------------------------------------------| | Service Name | Decorator | Development Status | |------------------------------------------------------------------------------| +| ACM | @mock_acm | all endpoints done | +|------------------------------------------------------------------------------| | API Gateway | @mock_apigateway | core endpoints done | |------------------------------------------------------------------------------| | Autoscaling | @mock_autoscaling| core endpoints done | @@ -78,10 +80,14 @@ It gets even better! Moto isn't just for Python code and it isn't just for S3. L | - Security Groups | | core endpoints done | | - Tags | | all endpoints done | |------------------------------------------------------------------------------| +| ECR | @mock_ecr | basic endpoints done | +|------------------------------------------------------------------------------| | ECS | @mock_ecs | basic endpoints done | |------------------------------------------------------------------------------| | ELB | @mock_elb | core endpoints done | |------------------------------------------------------------------------------| +| ELBv2 | @mock_elbv2 | core endpoints done | +|------------------------------------------------------------------------------| | EMR | @mock_emr | core endpoints done | |------------------------------------------------------------------------------| | Glacier | @mock_glacier | core endpoints done | @@ -90,10 +96,14 @@ It gets even better! Moto isn't just for Python code and it isn't just for S3. L |------------------------------------------------------------------------------| | Lambda | @mock_lambda | basic endpoints done | |------------------------------------------------------------------------------| +| Logs | @mock_logs | basic endpoints done | +|------------------------------------------------------------------------------| | Kinesis | @mock_kinesis | core endpoints done | |------------------------------------------------------------------------------| | KMS | @mock_kms | basic endpoints done | |------------------------------------------------------------------------------| +| Polly | @mock_polly | all endpoints done | +|------------------------------------------------------------------------------| | RDS | @mock_rds | core endpoints done | |------------------------------------------------------------------------------| | RDS2 | @mock_rds2 | core endpoints done | @@ -106,7 +116,7 @@ It gets even better! Moto isn't just for Python code and it isn't just for S3. L |------------------------------------------------------------------------------| | SES | @mock_ses | core endpoints done | |------------------------------------------------------------------------------| -| SNS | @mock_sns | core endpoints done | +| SNS | @mock_sns | all endpoints done | |------------------------------------------------------------------------------| | SQS | @mock_sqs | core endpoints done | |------------------------------------------------------------------------------| @@ -116,6 +126,8 @@ It gets even better! Moto isn't just for Python code and it isn't just for S3. L |------------------------------------------------------------------------------| | SWF | @mock_swf | basic endpoints done | |------------------------------------------------------------------------------| +| X-Ray | @mock_xray | core endpoints done | +|------------------------------------------------------------------------------| ``` ### Another Example @@ -153,9 +165,9 @@ moto 1.0.X mock docorators are defined for boto3 and do not work with boto2. Use Using moto with boto2 ```python -from moto import mock_ec2_deprecated +from moto import mock_ec2_deprecated import boto - + @mock_ec2_deprecated def test_something_with_ec2(): ec2_conn = boto.ec2.connect_to_region('us-east-1') diff --git a/moto/__init__.py b/moto/__init__.py index 728d8db71..64baa52ac 100644 --- a/moto/__init__.py +++ b/moto/__init__.py @@ -5,6 +5,7 @@ import logging __title__ = 'moto' __version__ = '1.0.1' +from .acm import mock_acm # flake8: noqa from .apigateway import mock_apigateway, mock_apigateway_deprecated # flake8: noqa from .autoscaling import mock_autoscaling, mock_autoscaling_deprecated # flake8: noqa from .awslambda import mock_lambda, mock_lambda_deprecated # flake8: noqa @@ -21,10 +22,11 @@ from .elbv2 import mock_elbv2 # flake8: noqa from .emr import mock_emr, mock_emr_deprecated # flake8: noqa from .events import mock_events # flake8: noqa from .glacier import mock_glacier, mock_glacier_deprecated # flake8: noqa -from .opsworks import mock_opsworks, mock_opsworks_deprecated # flake8: noqa from .iam import mock_iam, mock_iam_deprecated # flake8: noqa from .kinesis import mock_kinesis, mock_kinesis_deprecated # flake8: noqa from .kms import mock_kms, mock_kms_deprecated # flake8: noqa +from .opsworks import mock_opsworks, mock_opsworks_deprecated # flake8: noqa +from .polly import mock_polly # flake8: noqa from .rds import mock_rds, mock_rds_deprecated # flake8: noqa from .rds2 import mock_rds2, mock_rds2_deprecated # flake8: noqa from .redshift import mock_redshift, mock_redshift_deprecated # flake8: noqa @@ -36,6 +38,8 @@ from .sts import mock_sts, mock_sts_deprecated # flake8: noqa from .ssm import mock_ssm # flake8: noqa from .route53 import mock_route53, mock_route53_deprecated # flake8: noqa from .swf import mock_swf, mock_swf_deprecated # flake8: noqa +from .xray import mock_xray # flake8: noqa +from .logs import mock_logs, mock_logs_deprecated # flake8: noqa try: diff --git a/moto/acm/__init__.py b/moto/acm/__init__.py new file mode 100644 index 000000000..6cd8a4aa5 --- /dev/null +++ b/moto/acm/__init__.py @@ -0,0 +1,6 @@ +from __future__ import unicode_literals +from .models import acm_backends +from ..core.models import base_decorator + +acm_backend = acm_backends['us-east-1'] +mock_acm = base_decorator(acm_backends) diff --git a/moto/acm/models.py b/moto/acm/models.py new file mode 100644 index 000000000..de26529a4 --- /dev/null +++ b/moto/acm/models.py @@ -0,0 +1,395 @@ +from __future__ import unicode_literals + +import re +import json +import datetime +from moto.core import BaseBackend, BaseModel +from moto.ec2 import ec2_backends + +from .utils import make_arn_for_certificate + +import cryptography.x509 +import cryptography.hazmat.primitives.asymmetric.rsa +from cryptography.hazmat.primitives import serialization, hashes +from cryptography.hazmat.backends import default_backend + + +DEFAULT_ACCOUNT_ID = 123456789012 +GOOGLE_ROOT_CA = b"""-----BEGIN CERTIFICATE----- +MIIEKDCCAxCgAwIBAgIQAQAhJYiw+lmnd+8Fe2Yn3zANBgkqhkiG9w0BAQsFADBC +MQswCQYDVQQGEwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEbMBkGA1UEAxMS +R2VvVHJ1c3QgR2xvYmFsIENBMB4XDTE3MDUyMjExMzIzN1oXDTE4MTIzMTIzNTk1 +OVowSTELMAkGA1UEBhMCVVMxEzARBgNVBAoTCkdvb2dsZSBJbmMxJTAjBgNVBAMT +HEdvb2dsZSBJbnRlcm5ldCBBdXRob3JpdHkgRzIwggEiMA0GCSqGSIb3DQEBAQUA +A4IBDwAwggEKAoIBAQCcKgR3XNhQkToGo4Lg2FBIvIk/8RlwGohGfuCPxfGJziHu +Wv5hDbcyRImgdAtTT1WkzoJile7rWV/G4QWAEsRelD+8W0g49FP3JOb7kekVxM/0 +Uw30SvyfVN59vqBrb4fA0FAfKDADQNoIc1Fsf/86PKc3Bo69SxEE630k3ub5/DFx ++5TVYPMuSq9C0svqxGoassxT3RVLix/IGWEfzZ2oPmMrhDVpZYTIGcVGIvhTlb7j +gEoQxirsupcgEcc5mRAEoPBhepUljE5SdeK27QjKFPzOImqzTs9GA5eXA37Asd57 +r0Uzz7o+cbfe9CUlwg01iZ2d+w4ReYkeN8WvjnJpAgMBAAGjggERMIIBDTAfBgNV +HSMEGDAWgBTAephojYn7qwVkDBF9qn1luMrMTjAdBgNVHQ4EFgQUSt0GFhu89mi1 +dvWBtrtiGrpagS8wDgYDVR0PAQH/BAQDAgEGMC4GCCsGAQUFBwEBBCIwIDAeBggr +BgEFBQcwAYYSaHR0cDovL2cuc3ltY2QuY29tMBIGA1UdEwEB/wQIMAYBAf8CAQAw +NQYDVR0fBC4wLDAqoCigJoYkaHR0cDovL2cuc3ltY2IuY29tL2NybHMvZ3RnbG9i +YWwuY3JsMCEGA1UdIAQaMBgwDAYKKwYBBAHWeQIFATAIBgZngQwBAgIwHQYDVR0l +BBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMA0GCSqGSIb3DQEBCwUAA4IBAQDKSeWs +12Rkd1u+cfrP9B4jx5ppY1Rf60zWGSgjZGaOHMeHgGRfBIsmr5jfCnC8vBk97nsz +qX+99AXUcLsFJnnqmseYuQcZZTTMPOk/xQH6bwx+23pwXEz+LQDwyr4tjrSogPsB +E4jLnD/lu3fKOmc2887VJwJyQ6C9bgLxRwVxPgFZ6RGeGvOED4Cmong1L7bHon8X +fOGLVq7uZ4hRJzBgpWJSwzfVO+qFKgE4h6LPcK2kesnE58rF2rwjMvL+GMJ74N87 +L9TQEOaWTPtEtyFkDbkAlDASJodYmDkFOA/MgkgMCkdm7r+0X8T/cKjhf4t5K7hl +MqO5tzHpCvX2HzLc +-----END CERTIFICATE-----""" +# Added google root CA as AWS returns chain you gave it + root CA (provided or not) +# so for now a cheap response is just give any old root CA + + +def datetime_to_epoch(date): + # As only Py3 has datetime.timestamp() + return int((date - datetime.datetime(1970, 1, 1)).total_seconds()) + + +class AWSError(Exception): + TYPE = None + STATUS = 400 + + def __init__(self, message): + self.message = message + + def response(self): + resp = {'__type': self.TYPE, 'message': self.message} + return json.dumps(resp), dict(status=self.STATUS) + + +class AWSValidationException(AWSError): + TYPE = 'ValidationException' + + +class AWSResourceNotFoundException(AWSError): + TYPE = 'ResourceNotFoundException' + + +class CertBundle(BaseModel): + def __init__(self, certificate, private_key, chain=None, region='us-east-1', arn=None, cert_type='IMPORTED', cert_status='ISSUED'): + self.created_at = datetime.datetime.now() + self.cert = certificate + self._cert = None + self.common_name = None + self.key = private_key + self._key = None + self.chain = chain + self.tags = {} + self._chain = None + self.type = cert_type # Should really be an enum + self.status = cert_status # Should really be an enum + + # AWS always returns your chain + root CA + if self.chain is None: + self.chain = GOOGLE_ROOT_CA + else: + self.chain += b'\n' + GOOGLE_ROOT_CA + + # Takes care of PEM checking + self.validate_pk() + self.validate_certificate() + if chain is not None: + self.validate_chain() + + # TODO check cert is valid, or if self-signed then a chain is provided, otherwise + # raise AWSValidationException('Provided certificate is not a valid self signed. Please provide either a valid self-signed certificate or certificate chain.') + + # Used for when one wants to overwrite an arn + if arn is None: + self.arn = make_arn_for_certificate(DEFAULT_ACCOUNT_ID, region) + else: + self.arn = arn + + @classmethod + def generate_cert(cls, domain_name, sans=None): + if sans is None: + sans = set() + else: + sans = set(sans) + + sans.add(domain_name) + sans = [cryptography.x509.DNSName(item) for item in sans] + + key = cryptography.hazmat.primitives.asymmetric.rsa.generate_private_key(public_exponent=65537, key_size=2048, backend=default_backend()) + subject = cryptography.x509.Name([ + cryptography.x509.NameAttribute(cryptography.x509.NameOID.COUNTRY_NAME, u"US"), + cryptography.x509.NameAttribute(cryptography.x509.NameOID.STATE_OR_PROVINCE_NAME, u"CA"), + cryptography.x509.NameAttribute(cryptography.x509.NameOID.LOCALITY_NAME, u"San Francisco"), + cryptography.x509.NameAttribute(cryptography.x509.NameOID.ORGANIZATION_NAME, u"My Company"), + cryptography.x509.NameAttribute(cryptography.x509.NameOID.COMMON_NAME, domain_name), + ]) + issuer = cryptography.x509.Name([ # C = US, O = Amazon, OU = Server CA 1B, CN = Amazon + cryptography.x509.NameAttribute(cryptography.x509.NameOID.COUNTRY_NAME, u"US"), + cryptography.x509.NameAttribute(cryptography.x509.NameOID.ORGANIZATION_NAME, u"Amazon"), + cryptography.x509.NameAttribute(cryptography.x509.NameOID.ORGANIZATIONAL_UNIT_NAME, u"Server CA 1B"), + cryptography.x509.NameAttribute(cryptography.x509.NameOID.COMMON_NAME, u"Amazon"), + ]) + cert = cryptography.x509.CertificateBuilder().subject_name( + subject + ).issuer_name( + issuer + ).public_key( + key.public_key() + ).serial_number( + cryptography.x509.random_serial_number() + ).not_valid_before( + datetime.datetime.utcnow() + ).not_valid_after( + datetime.datetime.utcnow() + datetime.timedelta(days=365) + ).add_extension( + cryptography.x509.SubjectAlternativeName(sans), + critical=False, + ).sign(key, hashes.SHA512(), default_backend()) + + cert_armored = cert.public_bytes(serialization.Encoding.PEM) + private_key = key.private_bytes( + encoding=serialization.Encoding.PEM, + format=serialization.PrivateFormat.TraditionalOpenSSL, + encryption_algorithm=serialization.NoEncryption() + ) + + return cls(cert_armored, private_key, cert_type='AMAZON_ISSUED', cert_status='PENDING_VALIDATION') + + def validate_pk(self): + try: + self._key = serialization.load_pem_private_key(self.key, password=None, backend=default_backend()) + + if self._key.key_size > 2048: + AWSValidationException('The private key length is not supported. Only 1024-bit and 2048-bit are allowed.') + + except Exception as err: + if isinstance(err, AWSValidationException): + raise + raise AWSValidationException('The private key is not PEM-encoded or is not valid.') + + def validate_certificate(self): + try: + self._cert = cryptography.x509.load_pem_x509_certificate(self.cert, default_backend()) + + now = datetime.datetime.now() + if self._cert.not_valid_after < now: + raise AWSValidationException('The certificate has expired, is not valid.') + + if self._cert.not_valid_before > now: + raise AWSValidationException('The certificate is not in effect yet, is not valid.') + + # Extracting some common fields for ease of use + # Have to search through cert.subject for OIDs + self.common_name = self._cert.subject.get_attributes_for_oid(cryptography.x509.OID_COMMON_NAME)[0].value + + except Exception as err: + if isinstance(err, AWSValidationException): + raise + raise AWSValidationException('The certificate is not PEM-encoded or is not valid.') + + def validate_chain(self): + try: + self._chain = [] + + for cert_armored in self.chain.split(b'-\n-'): + # Would leave encoded but Py2 does not have raw binary strings + cert_armored = cert_armored.decode() + + # Fix missing -'s on split + cert_armored = re.sub(r'^----B', '-----B', cert_armored) + cert_armored = re.sub(r'E----$', 'E-----', cert_armored) + cert = cryptography.x509.load_pem_x509_certificate(cert_armored.encode(), default_backend()) + self._chain.append(cert) + + now = datetime.datetime.now() + if self._cert.not_valid_after < now: + raise AWSValidationException('The certificate chain has expired, is not valid.') + + if self._cert.not_valid_before > now: + raise AWSValidationException('The certificate chain is not in effect yet, is not valid.') + + except Exception as err: + if isinstance(err, AWSValidationException): + raise + raise AWSValidationException('The certificate is not PEM-encoded or is not valid.') + + def check(self): + # Basically, if the certificate is pending, and then checked again after 1 min + # It will appear as if its been validated + if self.type == 'AMAZON_ISSUED' and self.status == 'PENDING_VALIDATION' and \ + (datetime.datetime.now() - self.created_at).total_seconds() > 60: # 1min + self.status = 'ISSUED' + + def describe(self): + # 'RenewalSummary': {}, # Only when cert is amazon issued + if self._key.key_size == 1024: + key_algo = 'RSA_1024' + elif self._key.key_size == 2048: + key_algo = 'RSA_2048' + else: + key_algo = 'EC_prime256v1' + + # Look for SANs + san_obj = self._cert.extensions.get_extension_for_oid(cryptography.x509.OID_SUBJECT_ALTERNATIVE_NAME) + sans = [] + if san_obj is not None: + sans = [item.value for item in san_obj.value] + + result = { + 'Certificate': { + 'CertificateArn': self.arn, + 'DomainName': self.common_name, + 'InUseBy': [], + 'Issuer': self._cert.issuer.get_attributes_for_oid(cryptography.x509.OID_COMMON_NAME)[0].value, + 'KeyAlgorithm': key_algo, + 'NotAfter': datetime_to_epoch(self._cert.not_valid_after), + 'NotBefore': datetime_to_epoch(self._cert.not_valid_before), + 'Serial': self._cert.serial, + 'SignatureAlgorithm': self._cert.signature_algorithm_oid._name.upper().replace('ENCRYPTION', ''), + 'Status': self.status, # One of PENDING_VALIDATION, ISSUED, INACTIVE, EXPIRED, VALIDATION_TIMED_OUT, REVOKED, FAILED. + 'Subject': 'CN={0}'.format(self.common_name), + 'SubjectAlternativeNames': sans, + 'Type': self.type # One of IMPORTED, AMAZON_ISSUED + } + } + + if self.type == 'IMPORTED': + result['Certificate']['ImportedAt'] = datetime_to_epoch(self.created_at) + else: + result['Certificate']['CreatedAt'] = datetime_to_epoch(self.created_at) + result['Certificate']['IssuedAt'] = datetime_to_epoch(self.created_at) + + return result + + def __str__(self): + return self.arn + + def __repr__(self): + return '' + + +class AWSCertificateManagerBackend(BaseBackend): + def __init__(self, region): + super(AWSCertificateManagerBackend, self).__init__() + self.region = region + self._certificates = {} + self._idempotency_tokens = {} + + def reset(self): + region = self.region + self.__dict__ = {} + self.__init__(region) + + @staticmethod + def _arn_not_found(arn): + msg = 'Certificate with arn {0} not found in account {1}'.format(arn, DEFAULT_ACCOUNT_ID) + return AWSResourceNotFoundException(msg) + + def _get_arn_from_idempotency_token(self, token): + """ + If token doesnt exist, return None, later it will be + set with an expiry and arn. + + If token expiry has passed, delete entry and return None + + Else return ARN + + :param token: String token + :return: None or ARN + """ + now = datetime.datetime.now() + if token in self._idempotency_tokens: + if self._idempotency_tokens[token]['expires'] < now: + # Token has expired, new request + del self._idempotency_tokens[token] + return None + else: + return self._idempotency_tokens[token]['arn'] + + return None + + def _set_idempotency_token_arn(self, token, arn): + self._idempotency_tokens[token] = {'arn': arn, 'expires': datetime.datetime.now() + datetime.timedelta(hours=1)} + + def import_cert(self, certificate, private_key, chain=None, arn=None): + if arn is not None: + if arn not in self._certificates: + raise self._arn_not_found(arn) + else: + # Will reuse provided ARN + bundle = CertBundle(certificate, private_key, chain=chain, region=region, arn=arn) + else: + # Will generate a random ARN + bundle = CertBundle(certificate, private_key, chain=chain, region=region) + + self._certificates[bundle.arn] = bundle + + return bundle.arn + + def get_certificates_list(self): + """ + Get list of certificates + + :return: List of certificates + :rtype: list of CertBundle + """ + for arn in self._certificates.keys(): + yield self.get_certificate(arn) + + def get_certificate(self, arn): + if arn not in self._certificates: + raise self._arn_not_found(arn) + + cert_bundle = self._certificates[arn] + cert_bundle.check() + return cert_bundle + + def delete_certificate(self, arn): + if arn not in self._certificates: + raise self._arn_not_found(arn) + + del self._certificates[arn] + + def request_certificate(self, domain_name, domain_validation_options, idempotency_token, subject_alt_names): + if idempotency_token is not None: + arn = self._get_arn_from_idempotency_token(idempotency_token) + if arn is not None: + return arn + + cert = CertBundle.generate_cert(domain_name, subject_alt_names) + if idempotency_token is not None: + self._set_idempotency_token_arn(idempotency_token, cert.arn) + self._certificates[cert.arn] = cert + + return cert.arn + + def add_tags_to_certificate(self, arn, tags): + # get_cert does arn check + cert_bundle = self.get_certificate(arn) + + for tag in tags: + key = tag['Key'] + value = tag.get('Value', None) + cert_bundle.tags[key] = value + + def remove_tags_from_certificate(self, arn, tags): + # get_cert does arn check + cert_bundle = self.get_certificate(arn) + + for tag in tags: + key = tag['Key'] + value = tag.get('Value', None) + + try: + # If value isnt provided, just delete key + if value is None: + del cert_bundle.tags[key] + # If value is provided, only delete if it matches what already exists + elif cert_bundle.tags[key] == value: + del cert_bundle.tags[key] + except KeyError: + pass + + +acm_backends = {} +for region, ec2_backend in ec2_backends.items(): + acm_backends[region] = AWSCertificateManagerBackend(region) diff --git a/moto/acm/responses.py b/moto/acm/responses.py new file mode 100644 index 000000000..7bf12bbb8 --- /dev/null +++ b/moto/acm/responses.py @@ -0,0 +1,224 @@ +from __future__ import unicode_literals +import json +import base64 + +from moto.core.responses import BaseResponse +from .models import acm_backends, AWSError, AWSValidationException + + +class AWSCertificateManagerResponse(BaseResponse): + + @property + def acm_backend(self): + """ + ACM Backend + + :return: ACM Backend object + :rtype: moto.acm.models.AWSCertificateManagerBackend + """ + return acm_backends[self.region] + + @property + def request_params(self): + try: + return json.loads(self.body) + except ValueError: + return {} + + def _get_param(self, param, default=None): + return self.request_params.get(param, default) + + def add_tags_to_certificate(self): + arn = self._get_param('CertificateArn') + tags = self._get_param('Tags') + + if arn is None: + msg = 'A required parameter for the specified action is not supplied.' + return json.dumps({'__type': 'MissingParameter', 'message': msg}), dict(status=400) + + try: + self.acm_backend.add_tags_to_certificate(arn, tags) + except AWSError as err: + return err.response() + + return '' + + def delete_certificate(self): + arn = self._get_param('CertificateArn') + + if arn is None: + msg = 'A required parameter for the specified action is not supplied.' + return json.dumps({'__type': 'MissingParameter', 'message': msg}), dict(status=400) + + try: + self.acm_backend.delete_certificate(arn) + except AWSError as err: + return err.response() + + return '' + + def describe_certificate(self): + arn = self._get_param('CertificateArn') + + if arn is None: + msg = 'A required parameter for the specified action is not supplied.' + return json.dumps({'__type': 'MissingParameter', 'message': msg}), dict(status=400) + + try: + cert_bundle = self.acm_backend.get_certificate(arn) + except AWSError as err: + return err.response() + + return json.dumps(cert_bundle.describe()) + + def get_certificate(self): + arn = self._get_param('CertificateArn') + + if arn is None: + msg = 'A required parameter for the specified action is not supplied.' + return json.dumps({'__type': 'MissingParameter', 'message': msg}), dict(status=400) + + try: + cert_bundle = self.acm_backend.get_certificate(arn) + except AWSError as err: + return err.response() + + result = { + 'Certificate': cert_bundle.cert.decode(), + 'CertificateChain': cert_bundle.chain.decode() + } + return json.dumps(result) + + def import_certificate(self): + """ + Returns errors on: + Certificate, PrivateKey or Chain not being properly formatted + Arn not existing if its provided + PrivateKey size > 2048 + Certificate expired or is not yet in effect + + Does not return errors on: + Checking Certificate is legit, or a selfsigned chain is provided + + :return: str(JSON) for response + """ + certificate = self._get_param('Certificate') + private_key = self._get_param('PrivateKey') + chain = self._get_param('CertificateChain') # Optional + current_arn = self._get_param('CertificateArn') # Optional + + # Simple parameter decoding. Rather do it here as its a data transport decision not part of the + # actual data + try: + certificate = base64.standard_b64decode(certificate) + except: + return AWSValidationException('The certificate is not PEM-encoded or is not valid.').response() + try: + private_key = base64.standard_b64decode(private_key) + except: + return AWSValidationException('The private key is not PEM-encoded or is not valid.').response() + if chain is not None: + try: + chain = base64.standard_b64decode(chain) + except: + return AWSValidationException('The certificate chain is not PEM-encoded or is not valid.').response() + + try: + arn = self.acm_backend.import_cert(certificate, private_key, chain=chain, arn=current_arn) + except AWSError as err: + return err.response() + + return json.dumps({'CertificateArn': arn}) + + def list_certificates(self): + certs = [] + + for cert_bundle in self.acm_backend.get_certificates_list(): + certs.append({ + 'CertificateArn': cert_bundle.arn, + 'DomainName': cert_bundle.common_name + }) + + result = {'CertificateSummaryList': certs} + return json.dumps(result) + + def list_tags_for_certificate(self): + arn = self._get_param('CertificateArn') + + if arn is None: + msg = 'A required parameter for the specified action is not supplied.' + return {'__type': 'MissingParameter', 'message': msg}, dict(status=400) + + try: + cert_bundle = self.acm_backend.get_certificate(arn) + except AWSError as err: + return err.response() + + result = {'Tags': []} + # Tag "objects" can not contain the Value part + for key, value in cert_bundle.tags.items(): + tag_dict = {'Key': key} + if value is not None: + tag_dict['Value'] = value + result['Tags'].append(tag_dict) + + return json.dumps(result) + + def remove_tags_from_certificate(self): + arn = self._get_param('CertificateArn') + tags = self._get_param('Tags') + + if arn is None: + msg = 'A required parameter for the specified action is not supplied.' + return json.dumps({'__type': 'MissingParameter', 'message': msg}), dict(status=400) + + try: + self.acm_backend.remove_tags_from_certificate(arn, tags) + except AWSError as err: + return err.response() + + return '' + + def request_certificate(self): + domain_name = self._get_param('DomainName') + domain_validation_options = self._get_param('DomainValidationOptions') # is ignored atm + idempotency_token = self._get_param('IdempotencyToken') + subject_alt_names = self._get_param('SubjectAlternativeNames') + + if len(subject_alt_names) > 10: + # There is initial AWS limit of 10 + msg = 'An ACM limit has been exceeded. Need to request SAN limit to be raised' + return json.dumps({'__type': 'LimitExceededException', 'message': msg}), dict(status=400) + + try: + arn = self.acm_backend.request_certificate(domain_name, domain_validation_options, idempotency_token, subject_alt_names) + except AWSError as err: + return err.response() + + return json.dumps({'CertificateArn': arn}) + + def resend_validation_email(self): + arn = self._get_param('CertificateArn') + domain = self._get_param('Domain') + # ValidationDomain not used yet. + # Contains domain which is equal to or a subset of Domain + # that AWS will send validation emails to + # https://docs.aws.amazon.com/acm/latest/APIReference/API_ResendValidationEmail.html + # validation_domain = self._get_param('ValidationDomain') + + if arn is None: + msg = 'A required parameter for the specified action is not supplied.' + return json.dumps({'__type': 'MissingParameter', 'message': msg}), dict(status=400) + + try: + cert_bundle = self.acm_backend.get_certificate(arn) + + if cert_bundle.common_name != domain: + msg = 'Parameter Domain does not match certificate domain' + _type = 'InvalidDomainValidationOptionsException' + return json.dumps({'__type': _type, 'message': msg}), dict(status=400) + + except AWSError as err: + return err.response() + + return '' diff --git a/moto/acm/urls.py b/moto/acm/urls.py new file mode 100644 index 000000000..20acbb3f4 --- /dev/null +++ b/moto/acm/urls.py @@ -0,0 +1,10 @@ +from __future__ import unicode_literals +from .responses import AWSCertificateManagerResponse + +url_bases = [ + "https?://acm.(.+).amazonaws.com", +] + +url_paths = { + '{0}/$': AWSCertificateManagerResponse.dispatch, +} diff --git a/moto/acm/utils.py b/moto/acm/utils.py new file mode 100644 index 000000000..b3c441454 --- /dev/null +++ b/moto/acm/utils.py @@ -0,0 +1,7 @@ +import uuid + + +def make_arn_for_certificate(account_id, region_name): + # Example + # arn:aws:acm:eu-west-2:764371465172:certificate/c4b738b8-56fe-4b3a-b841-1c047654780b + return "arn:aws:acm:{0}:{1}:certificate/{2}".format(region_name, account_id, uuid.uuid4()) diff --git a/moto/awslambda/models.py b/moto/awslambda/models.py index 13d4726ac..d22d1a7f4 100644 --- a/moto/awslambda/models.py +++ b/moto/awslambda/models.py @@ -1,34 +1,150 @@ from __future__ import unicode_literals import base64 +from collections import defaultdict import datetime +import docker.errors import hashlib import io +import logging import os import json -import sys +import re import zipfile - -try: - from StringIO import StringIO -except: - from io import StringIO +import uuid +import functools +import tarfile +import calendar +import threading +import traceback +import requests.adapters import boto.awslambda from moto.core import BaseBackend, BaseModel +from moto.core.utils import unix_time_millis from moto.s3.models import s3_backend +from moto.logs.models import logs_backends from moto.s3.exceptions import MissingBucket, MissingKey +from moto import settings + +logger = logging.getLogger(__name__) + + +try: + from tempfile import TemporaryDirectory +except ImportError: + from backports.tempfile import TemporaryDirectory + + +_stderr_regex = re.compile(r'START|END|REPORT RequestId: .*') +_orig_adapter_send = requests.adapters.HTTPAdapter.send + + +def zip2tar(zip_bytes): + with TemporaryDirectory() as td: + tarname = os.path.join(td, 'data.tar') + timeshift = int((datetime.datetime.now() - + datetime.datetime.utcnow()).total_seconds()) + with zipfile.ZipFile(io.BytesIO(zip_bytes), 'r') as zipf, \ + tarfile.TarFile(tarname, 'w') as tarf: + for zipinfo in zipf.infolist(): + if zipinfo.filename[-1] == '/': # is_dir() is py3.6+ + continue + + tarinfo = tarfile.TarInfo(name=zipinfo.filename) + tarinfo.size = zipinfo.file_size + tarinfo.mtime = calendar.timegm(zipinfo.date_time) - timeshift + infile = zipf.open(zipinfo.filename) + tarf.addfile(tarinfo, infile) + + with open(tarname, 'rb') as f: + tar_data = f.read() + return tar_data + + +class _VolumeRefCount: + __slots__ = "refcount", "volume" + + def __init__(self, refcount, volume): + self.refcount = refcount + self.volume = volume + + +class _DockerDataVolumeContext: + _data_vol_map = defaultdict(lambda: _VolumeRefCount(0, None)) # {sha256: _VolumeRefCount} + _lock = threading.Lock() + + def __init__(self, lambda_func): + self._lambda_func = lambda_func + self._vol_ref = None + + @property + def name(self): + return self._vol_ref.volume.name + + def __enter__(self): + # See if volume is already known + with self.__class__._lock: + self._vol_ref = self.__class__._data_vol_map[self._lambda_func.code_sha_256] + self._vol_ref.refcount += 1 + if self._vol_ref.refcount > 1: + return self + + # See if the volume already exists + for vol in self._lambda_func.docker_client.volumes.list(): + if vol.name == self._lambda_func.code_sha_256: + self._vol_ref.volume = vol + return self + + # It doesn't exist so we need to create it + self._vol_ref.volume = self._lambda_func.docker_client.volumes.create(self._lambda_func.code_sha_256) + container = self._lambda_func.docker_client.containers.run('alpine', 'sleep 100', volumes={self.name: '/tmp/data'}, detach=True) + try: + tar_bytes = zip2tar(self._lambda_func.code_bytes) + container.put_archive('/tmp/data', tar_bytes) + finally: + container.remove(force=True) + + return self + + def __exit__(self, exc_type, exc_val, exc_tb): + with self.__class__._lock: + self._vol_ref.refcount -= 1 + if self._vol_ref.refcount == 0: + try: + self._vol_ref.volume.remove() + except docker.errors.APIError as e: + if e.status_code != 409: + raise + + raise # multiple processes trying to use same volume? class LambdaFunction(BaseModel): - - def __init__(self, spec, validate_s3=True): + def __init__(self, spec, region, validate_s3=True): # required + self.region = region self.code = spec['Code'] self.function_name = spec['FunctionName'] self.handler = spec['Handler'] self.role = spec['Role'] self.run_time = spec['Runtime'] + self.logs_backend = logs_backends[self.region] + self.environment_vars = spec.get('Environment', {}).get('Variables', {}) + self.docker_client = docker.from_env() + + # Unfortunately mocking replaces this method w/o fallback enabled, so we + # need to replace it if we detect it's been mocked + if requests.adapters.HTTPAdapter.send != _orig_adapter_send: + _orig_get_adapter = self.docker_client.api.get_adapter + + def replace_adapter_send(*args, **kwargs): + adapter = _orig_get_adapter(*args, **kwargs) + + if isinstance(adapter, requests.adapters.HTTPAdapter): + adapter.send = functools.partial(_orig_adapter_send, adapter) + return adapter + self.docker_client.api.get_adapter = replace_adapter_send # optional self.description = spec.get('Description', '') @@ -36,13 +152,18 @@ class LambdaFunction(BaseModel): self.publish = spec.get('Publish', False) # this is ignored currently self.timeout = spec.get('Timeout', 3) + self.logs_group_name = '/aws/lambda/{}'.format(self.function_name) + self.logs_backend.ensure_log_group(self.logs_group_name, []) + # this isn't finished yet. it needs to find out the VpcId value self._vpc_config = spec.get( 'VpcConfig', {'SubnetIds': [], 'SecurityGroupIds': []}) # auto-generated self.version = '$LATEST' - self.last_modified = datetime.datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S') + self.last_modified = datetime.datetime.utcnow().strftime( + '%Y-%m-%d %H:%M:%S') + if 'ZipFile' in self.code: # more hackery to handle unicode/bytes/str in python3 and python2 - # argh! @@ -52,12 +173,13 @@ class LambdaFunction(BaseModel): except Exception: to_unzip_code = base64.b64decode(self.code['ZipFile']) - zbuffer = io.BytesIO() - zbuffer.write(to_unzip_code) - zip_file = zipfile.ZipFile(zbuffer, 'r', zipfile.ZIP_DEFLATED) - self.code = zip_file.read("".join(zip_file.namelist())) + self.code_bytes = to_unzip_code self.code_size = len(to_unzip_code) self.code_sha_256 = hashlib.sha256(to_unzip_code).hexdigest() + + # TODO: we should be putting this in a lambda bucket + self.code['UUID'] = str(uuid.uuid4()) + self.code['S3Key'] = '{}-{}'.format(self.function_name, self.code['UUID']) else: # validate s3 bucket and key key = None @@ -76,10 +198,14 @@ class LambdaFunction(BaseModel): "InvalidParameterValueException", "Error occurred while GetObject. S3 Error Code: NoSuchKey. S3 Error Message: The specified key does not exist.") if key: + self.code_bytes = key.value self.code_size = key.size self.code_sha_256 = hashlib.sha256(key.value).hexdigest() - self.function_arn = 'arn:aws:lambda:123456789012:function:{0}'.format( - self.function_name) + + self.function_arn = 'arn:aws:lambda:{}:123456789012:function:{}'.format( + self.region, self.function_name) + + self.tags = dict() @property def vpc_config(self): @@ -92,7 +218,7 @@ class LambdaFunction(BaseModel): return json.dumps(self.get_configuration()) def get_configuration(self): - return { + config = { "CodeSha256": self.code_sha_256, "CodeSize": self.code_size, "Description": self.description, @@ -108,65 +234,105 @@ class LambdaFunction(BaseModel): "VpcConfig": self.vpc_config, } + if self.environment_vars: + config['Environment'] = { + 'Variables': self.environment_vars + } + + return config + def get_code(self): return { "Code": { - "Location": "s3://lambda-functions.aws.amazon.com/{0}".format(self.code['S3Key']), + "Location": "s3://awslambda-{0}-tasks.s3-{0}.amazonaws.com/{1}".format(self.region, self.code['S3Key']), "RepositoryType": "S3" }, "Configuration": self.get_configuration(), } - def convert(self, s): + @staticmethod + def convert(s): try: return str(s, encoding='utf-8') except: return s - def is_json(self, test_str): + @staticmethod + def is_json(test_str): try: response = json.loads(test_str) except: response = test_str return response - def _invoke_lambda(self, code, event={}, context={}): - # TO DO: context not yet implemented - try: - mycode = "\n".join(['import json', - self.convert(self.code), - self.convert('print(json.dumps(lambda_handler(%s, %s)))' % (self.is_json(self.convert(event)), context))]) + def _invoke_lambda(self, code, event=None, context=None): + # TODO: context not yet implemented + if event is None: + event = dict() + if context is None: + context = {} - except Exception as ex: - print("Exception %s", ex) - - errored = False try: - original_stdout = sys.stdout - original_stderr = sys.stderr - codeOut = StringIO() - codeErr = StringIO() - sys.stdout = codeOut - sys.stderr = codeErr - exec(mycode) - exec_err = codeErr.getvalue() - exec_out = codeOut.getvalue() - result = self.convert(exec_out.strip()) - if exec_err: - result = "\n".join([exec_out.strip(), self.convert(exec_err)]) - except Exception as ex: - errored = True - result = '%s\n\n\nException %s' % (mycode, ex) - finally: - codeErr.close() - codeOut.close() - sys.stdout = original_stdout - sys.stderr = original_stderr - return self.convert(result), errored + # TODO: I believe we can keep the container running and feed events as needed + # also need to hook it up to the other services so it can make kws/s3 etc calls + # Should get invoke_id /RequestId from invovation + env_vars = { + "AWS_LAMBDA_FUNCTION_TIMEOUT": self.timeout, + "AWS_LAMBDA_FUNCTION_NAME": self.function_name, + "AWS_LAMBDA_FUNCTION_MEMORY_SIZE": self.memory_size, + "AWS_LAMBDA_FUNCTION_VERSION": self.version, + "AWS_REGION": self.region, + } + + env_vars.update(self.environment_vars) + + container = output = exit_code = None + with _DockerDataVolumeContext(self) as data_vol: + try: + run_kwargs = dict(links={'motoserver': 'motoserver'}) if settings.TEST_SERVER_MODE else {} + container = self.docker_client.containers.run( + "lambci/lambda:{}".format(self.run_time), + [self.handler, json.dumps(event)], remove=False, + mem_limit="{}m".format(self.memory_size), + volumes=["{}:/var/task".format(data_vol.name)], environment=env_vars, detach=True, **run_kwargs) + finally: + if container: + exit_code = container.wait() + output = container.logs(stdout=False, stderr=True) + output += container.logs(stdout=True, stderr=False) + container.remove() + + output = output.decode('utf-8') + + # Send output to "logs" backend + invoke_id = uuid.uuid4().hex + log_stream_name = "{date.year}/{date.month:02d}/{date.day:02d}/[{version}]{invoke_id}".format( + date=datetime.datetime.utcnow(), version=self.version, invoke_id=invoke_id + ) + + self.logs_backend.create_log_stream(self.logs_group_name, log_stream_name) + + log_events = [{'timestamp': unix_time_millis(), "message": line} + for line in output.splitlines()] + self.logs_backend.put_log_events(self.logs_group_name, log_stream_name, log_events, None) + + if exit_code != 0: + raise Exception( + 'lambda invoke failed output: {}'.format(output)) + + # strip out RequestId lines + output = os.linesep.join([line for line in self.convert(output).splitlines() if not _stderr_regex.match(line)]) + return output, False + except BaseException as e: + traceback.print_exc() + return "error running lambda: {}".format(e), True def invoke(self, body, request_headers, response_headers): payload = dict() + if body: + body = json.loads(body) + # Get the invocation type: res, errored = self._invoke_lambda(code=self.code, event=body) if request_headers.get("x-amz-invocation-type") == "RequestResponse": @@ -182,7 +348,8 @@ class LambdaFunction(BaseModel): return result @classmethod - def create_from_cloudformation_json(cls, resource_name, cloudformation_json, region_name): + def create_from_cloudformation_json(cls, resource_name, cloudformation_json, + region_name): properties = cloudformation_json['Properties'] # required @@ -205,17 +372,19 @@ class LambdaFunction(BaseModel): # this snippet converts this plaintext code to a proper base64-encoded ZIP file. if 'ZipFile' in properties['Code']: spec['Code']['ZipFile'] = base64.b64encode( - cls._create_zipfile_from_plaintext_code(spec['Code']['ZipFile'])) + cls._create_zipfile_from_plaintext_code( + spec['Code']['ZipFile'])) backend = lambda_backends[region_name] fn = backend.create_function(spec) return fn def get_cfn_attribute(self, attribute_name): - from moto.cloudformation.exceptions import UnformattedGetAttTemplateException + from moto.cloudformation.exceptions import \ + UnformattedGetAttTemplateException if attribute_name == 'Arn': - region = 'us-east-1' - return 'arn:aws:lambda:{0}:123456789012:function:{1}'.format(region, self.function_name) + return 'arn:aws:lambda:{0}:123456789012:function:{1}'.format( + self.region, self.function_name) raise UnformattedGetAttTemplateException() @staticmethod @@ -229,7 +398,6 @@ class LambdaFunction(BaseModel): class EventSourceMapping(BaseModel): - def __init__(self, spec): # required self.function_name = spec['FunctionName'] @@ -239,10 +407,12 @@ class EventSourceMapping(BaseModel): # optional self.batch_size = spec.get('BatchSize', 100) self.enabled = spec.get('Enabled', True) - self.starting_position_timestamp = spec.get('StartingPositionTimestamp', None) + self.starting_position_timestamp = spec.get('StartingPositionTimestamp', + None) @classmethod - def create_from_cloudformation_json(cls, resource_name, cloudformation_json, region_name): + def create_from_cloudformation_json(cls, resource_name, cloudformation_json, + region_name): properties = cloudformation_json['Properties'] spec = { 'FunctionName': properties['FunctionName'], @@ -257,12 +427,12 @@ class EventSourceMapping(BaseModel): class LambdaVersion(BaseModel): - def __init__(self, spec): self.version = spec['Version'] @classmethod - def create_from_cloudformation_json(cls, resource_name, cloudformation_json, region_name): + def create_from_cloudformation_json(cls, resource_name, cloudformation_json, + region_name): properties = cloudformation_json['Properties'] spec = { 'Version': properties.get('Version') @@ -271,36 +441,99 @@ class LambdaVersion(BaseModel): class LambdaBackend(BaseBackend): - - def __init__(self): + def __init__(self, region_name): self._functions = {} + self.region_name = region_name + + def reset(self): + region_name = self.region_name + self.__dict__ = {} + self.__init__(region_name) def has_function(self, function_name): return function_name in self._functions + def has_function_arn(self, function_arn): + return self.get_function_by_arn(function_arn) is not None + def create_function(self, spec): - fn = LambdaFunction(spec) + fn = LambdaFunction(spec, self.region_name) self._functions[fn.function_name] = fn return fn def get_function(self, function_name): return self._functions[function_name] + def get_function_by_arn(self, function_arn): + for function in self._functions.values(): + if function.function_arn == function_arn: + return function + return None + def delete_function(self, function_name): del self._functions[function_name] def list_functions(self): return self._functions.values() + def send_message(self, function_name, message): + event = { + "Records": [ + { + "EventVersion": "1.0", + "EventSubscriptionArn": "arn:aws:sns:EXAMPLE", + "EventSource": "aws:sns", + "Sns": { + "SignatureVersion": "1", + "Timestamp": "1970-01-01T00:00:00.000Z", + "Signature": "EXAMPLE", + "SigningCertUrl": "EXAMPLE", + "MessageId": "95df01b4-ee98-5cb9-9903-4c221d41eb5e", + "Message": message, + "MessageAttributes": { + "Test": { + "Type": "String", + "Value": "TestString" + }, + "TestBinary": { + "Type": "Binary", + "Value": "TestBinary" + } + }, + "Type": "Notification", + "UnsubscribeUrl": "EXAMPLE", + "TopicArn": "arn:aws:sns:EXAMPLE", + "Subject": "TestInvoke" + } + } + ] + + } + self._functions[function_name].invoke(json.dumps(event), {}, {}) + pass + + def list_tags(self, resource): + return self.get_function_by_arn(resource).tags + + def tag_resource(self, resource, tags): + self.get_function_by_arn(resource).tags.update(tags) + + def untag_resource(self, resource, tagKeys): + function = self.get_function_by_arn(resource) + for key in tagKeys: + try: + del function.tags[key] + except KeyError: + pass + # Don't care + def do_validate_s3(): return os.environ.get('VALIDATE_LAMBDA_S3', '') in ['', '1', 'true'] -lambda_backends = {} -for region in boto.awslambda.regions(): - lambda_backends[region.name] = LambdaBackend() - # Handle us forgotten regions, unless Lambda truly only runs out of US and -for region in ['ap-southeast-2']: - lambda_backends[region] = LambdaBackend() +lambda_backends = {_region.name: LambdaBackend(_region.name) + for _region in boto.awslambda.regions()} + +lambda_backends['ap-southeast-2'] = LambdaBackend('ap-southeast-2') diff --git a/moto/awslambda/responses.py b/moto/awslambda/responses.py index d145f4760..cf92e66f4 100644 --- a/moto/awslambda/responses.py +++ b/moto/awslambda/responses.py @@ -3,6 +3,12 @@ from __future__ import unicode_literals import json import re +try: + from urllib import unquote + from urlparse import urlparse, parse_qs +except: + from urllib.parse import unquote, urlparse, parse_qs + from moto.core.responses import BaseResponse @@ -33,6 +39,24 @@ class LambdaResponse(BaseResponse): else: raise ValueError("Cannot handle request") + def invoke_async(self, request, full_url, headers): + self.setup_class(request, full_url, headers) + if request.method == 'POST': + return self._invoke_async(request, full_url) + else: + raise ValueError("Cannot handle request") + + def tag(self, request, full_url, headers): + self.setup_class(request, full_url, headers) + if request.method == 'GET': + return self._list_tags(request, full_url) + elif request.method == 'POST': + return self._tag_resource(request, full_url) + elif request.method == 'DELETE': + return self._untag_resource(request, full_url) + else: + raise ValueError("Cannot handle {0} request".format(request.method)) + def _invoke(self, request, full_url): response_headers = {} lambda_backend = self.get_lambda_backend(full_url) @@ -48,6 +72,20 @@ class LambdaResponse(BaseResponse): else: return 404, response_headers, "{}" + def _invoke_async(self, request, full_url): + response_headers = {} + lambda_backend = self.get_lambda_backend(full_url) + + path = request.path if hasattr(request, 'path') else request.path_url + function_name = path.split('/')[-3] + if lambda_backend.has_function(function_name): + fn = lambda_backend.get_function(function_name) + fn.invoke(self.body, self.headers, response_headers) + response_headers['Content-Length'] = str(0) + return 202, response_headers, "" + else: + return 404, response_headers, "{}" + def _list_functions(self, request, full_url, headers): lambda_backend = self.get_lambda_backend(full_url) return 200, {}, json.dumps({ @@ -102,3 +140,43 @@ class LambdaResponse(BaseResponse): return region.group(1) else: return self.default_region + + def _list_tags(self, request, full_url): + lambda_backend = self.get_lambda_backend(full_url) + + path = request.path if hasattr(request, 'path') else request.path_url + function_arn = unquote(path.split('/')[-1]) + + if lambda_backend.has_function_arn(function_arn): + function = lambda_backend.get_function_by_arn(function_arn) + return 200, {}, json.dumps(dict(Tags=function.tags)) + else: + return 404, {}, "{}" + + def _tag_resource(self, request, full_url): + lambda_backend = self.get_lambda_backend(full_url) + + path = request.path if hasattr(request, 'path') else request.path_url + function_arn = unquote(path.split('/')[-1]) + + spec = json.loads(self.body) + + if lambda_backend.has_function_arn(function_arn): + lambda_backend.tag_resource(function_arn, spec['Tags']) + return 200, {}, "{}" + else: + return 404, {}, "{}" + + def _untag_resource(self, request, full_url): + lambda_backend = self.get_lambda_backend(full_url) + + path = request.path if hasattr(request, 'path') else request.path_url + function_arn = unquote(path.split('/')[-1].split('?')[0]) + + tag_keys = parse_qs(urlparse(full_url).query)['tagKeys'] + + if lambda_backend.has_function_arn(function_arn): + lambda_backend.untag_resource(function_arn, tag_keys) + return 204, {}, "{}" + else: + return 404, {}, "{}" diff --git a/moto/awslambda/urls.py b/moto/awslambda/urls.py index c63135766..0fec24bab 100644 --- a/moto/awslambda/urls.py +++ b/moto/awslambda/urls.py @@ -9,6 +9,8 @@ response = LambdaResponse() url_paths = { '{0}/(?P[^/]+)/functions/?$': response.root, - '{0}/(?P[^/]+)/functions/(?P[\w_-]+)/?$': response.function, - '{0}/(?P[^/]+)/functions/(?P[\w_-]+)/invocations/?$': response.invoke, + r'{0}/(?P[^/]+)/functions/(?P[\w_-]+)/?$': response.function, + r'{0}/(?P[^/]+)/functions/(?P[\w_-]+)/invocations/?$': response.invoke, + r'{0}/(?P[^/]+)/functions/(?P[\w_-]+)/invoke-async/?$': response.invoke_async, + r'{0}/(?P[^/]+)/tags/(?P.+)': response.tag } diff --git a/moto/backends.py b/moto/backends.py index b452b45fd..24a8b6c2b 100644 --- a/moto/backends.py +++ b/moto/backends.py @@ -1,5 +1,6 @@ from __future__ import unicode_literals +from moto.acm import acm_backends from moto.apigateway import apigateway_backends from moto.autoscaling import autoscaling_backends from moto.awslambda import lambda_backends @@ -21,7 +22,9 @@ from moto.iam import iam_backends from moto.instance_metadata import instance_metadata_backends from moto.kinesis import kinesis_backends from moto.kms import kms_backends +from moto.logs import logs_backends from moto.opsworks import opsworks_backends +from moto.polly import polly_backends from moto.rds2 import rds2_backends from moto.redshift import redshift_backends from moto.route53 import route53_backends @@ -31,8 +34,10 @@ from moto.sns import sns_backends from moto.sqs import sqs_backends from moto.ssm import ssm_backends from moto.sts import sts_backends +from moto.xray import xray_backends BACKENDS = { + 'acm': acm_backends, 'apigateway': apigateway_backends, 'autoscaling': autoscaling_backends, 'cloudformation': cloudformation_backends, @@ -51,9 +56,11 @@ BACKENDS = { 'iam': iam_backends, 'moto_api': moto_api_backends, 'instance_metadata': instance_metadata_backends, - 'opsworks': opsworks_backends, + 'logs': logs_backends, 'kinesis': kinesis_backends, 'kms': kms_backends, + 'opsworks': opsworks_backends, + 'polly': polly_backends, 'redshift': redshift_backends, 'rds': rds2_backends, 's3': s3_backends, @@ -65,6 +72,7 @@ BACKENDS = { 'sts': sts_backends, 'route53': route53_backends, 'lambda': lambda_backends, + 'xray': xray_backends } diff --git a/moto/cloudformation/models.py b/moto/cloudformation/models.py index ec922d8f5..e579e4c08 100644 --- a/moto/cloudformation/models.py +++ b/moto/cloudformation/models.py @@ -9,7 +9,7 @@ from moto.compat import OrderedDict from moto.core import BaseBackend, BaseModel from .parsing import ResourceMap, OutputMap -from .utils import generate_stack_id +from .utils import generate_stack_id, yaml_tag_constructor from .exceptions import ValidationError @@ -74,6 +74,7 @@ class FakeStack(BaseModel): )) def _parse_template(self): + yaml.add_multi_constructor('', yaml_tag_constructor) try: self.template_dict = yaml.load(self.template) except yaml.parser.ParserError: diff --git a/moto/cloudformation/responses.py b/moto/cloudformation/responses.py index d66a172a8..423cf92c1 100644 --- a/moto/cloudformation/responses.py +++ b/moto/cloudformation/responses.py @@ -391,8 +391,7 @@ LIST_STACKS_RESOURCES_RESPONSE = """ GET_TEMPLATE_RESPONSE_TEMPLATE = """ - {{ stack.template }} - + {{ stack.template }} b9b4b068-3a41-11e5-94eb-example diff --git a/moto/cloudformation/utils.py b/moto/cloudformation/utils.py index 1d629c76b..384ea5401 100644 --- a/moto/cloudformation/utils.py +++ b/moto/cloudformation/utils.py @@ -2,6 +2,7 @@ from __future__ import unicode_literals import uuid import six import random +import yaml def generate_stack_id(stack_name): @@ -13,3 +14,22 @@ def random_suffix(): size = 12 chars = list(range(10)) + ['A-Z'] return ''.join(six.text_type(random.choice(chars)) for x in range(size)) + + +def yaml_tag_constructor(loader, tag, node): + """convert shorthand intrinsic function to full name + """ + def _f(loader, tag, node): + if tag == '!GetAtt': + return node.value.split('.') + elif type(node) == yaml.SequenceNode: + return loader.construct_sequence(node) + else: + return node.value + + if tag == '!Ref': + key = 'Ref' + else: + key = 'Fn::{}'.format(tag[1:]) + + return {key: _f(loader, tag, node)} diff --git a/moto/cloudwatch/models.py b/moto/cloudwatch/models.py index ed0086d93..ac328def2 100644 --- a/moto/cloudwatch/models.py +++ b/moto/cloudwatch/models.py @@ -2,6 +2,11 @@ from moto.core import BaseBackend, BaseModel import boto.ec2.cloudwatch import datetime +from .utils import make_arn_for_dashboard + + +DEFAULT_ACCOUNT_ID = 123456789012 + class Dimension(object): @@ -44,10 +49,34 @@ class MetricDatum(BaseModel): 'value']) for dimension in dimensions] +class Dashboard(BaseModel): + def __init__(self, name, body): + # Guaranteed to be unique for now as the name is also the key of a dictionary where they are stored + self.arn = make_arn_for_dashboard(DEFAULT_ACCOUNT_ID, name) + self.name = name + self.body = body + self.last_modified = datetime.datetime.now() + + @property + def last_modified_iso(self): + return self.last_modified.isoformat() + + @property + def size(self): + return len(self) + + def __len__(self): + return len(self.body) + + def __repr__(self): + return ''.format(self.name) + + class CloudWatchBackend(BaseBackend): def __init__(self): self.alarms = {} + self.dashboards = {} self.metric_data = [] def put_metric_alarm(self, name, namespace, metric_name, comparison_operator, evaluation_periods, @@ -110,6 +139,31 @@ class CloudWatchBackend(BaseBackend): def get_all_metrics(self): return self.metric_data + def put_dashboard(self, name, body): + self.dashboards[name] = Dashboard(name, body) + + def list_dashboards(self, prefix=''): + for key, value in self.dashboards.items(): + if key.startswith(prefix): + yield value + + def delete_dashboards(self, dashboards): + to_delete = set(dashboards) + all_dashboards = set(self.dashboards.keys()) + + left_over = to_delete - all_dashboards + if len(left_over) > 0: + # Some dashboards are not found + return False, 'The specified dashboard does not exist. [{0}]'.format(', '.join(left_over)) + + for dashboard in to_delete: + del self.dashboards[dashboard] + + return True, None + + def get_dashboard(self, dashboard): + return self.dashboards.get(dashboard) + class LogGroup(BaseModel): diff --git a/moto/cloudwatch/responses.py b/moto/cloudwatch/responses.py index d06fe21d7..cd7ce123e 100644 --- a/moto/cloudwatch/responses.py +++ b/moto/cloudwatch/responses.py @@ -1,9 +1,18 @@ +import json from moto.core.responses import BaseResponse from .models import cloudwatch_backends class CloudWatchResponse(BaseResponse): + @property + def cloudwatch_backend(self): + return cloudwatch_backends[self.region] + + def _error(self, code, message, status=400): + template = self.response_template(ERROR_RESPONSE_TEMPLATE) + return template.render(code=code, message=message), dict(status=status) + def put_metric_alarm(self): name = self._get_param('AlarmName') namespace = self._get_param('Namespace') @@ -20,15 +29,14 @@ class CloudWatchResponse(BaseResponse): insufficient_data_actions = self._get_multi_param( "InsufficientDataActions.member") unit = self._get_param('Unit') - cloudwatch_backend = cloudwatch_backends[self.region] - alarm = cloudwatch_backend.put_metric_alarm(name, namespace, metric_name, - comparison_operator, - evaluation_periods, period, - threshold, statistic, - description, dimensions, - alarm_actions, ok_actions, - insufficient_data_actions, - unit) + alarm = self.cloudwatch_backend.put_metric_alarm(name, namespace, metric_name, + comparison_operator, + evaluation_periods, period, + threshold, statistic, + description, dimensions, + alarm_actions, ok_actions, + insufficient_data_actions, + unit) template = self.response_template(PUT_METRIC_ALARM_TEMPLATE) return template.render(alarm=alarm) @@ -37,28 +45,26 @@ class CloudWatchResponse(BaseResponse): alarm_name_prefix = self._get_param('AlarmNamePrefix') alarm_names = self._get_multi_param('AlarmNames.member') state_value = self._get_param('StateValue') - cloudwatch_backend = cloudwatch_backends[self.region] if action_prefix: - alarms = cloudwatch_backend.get_alarms_by_action_prefix( + alarms = self.cloudwatch_backend.get_alarms_by_action_prefix( action_prefix) elif alarm_name_prefix: - alarms = cloudwatch_backend.get_alarms_by_alarm_name_prefix( + alarms = self.cloudwatch_backend.get_alarms_by_alarm_name_prefix( alarm_name_prefix) elif alarm_names: - alarms = cloudwatch_backend.get_alarms_by_alarm_names(alarm_names) + alarms = self.cloudwatch_backend.get_alarms_by_alarm_names(alarm_names) elif state_value: - alarms = cloudwatch_backend.get_alarms_by_state_value(state_value) + alarms = self.cloudwatch_backend.get_alarms_by_state_value(state_value) else: - alarms = cloudwatch_backend.get_all_alarms() + alarms = self.cloudwatch_backend.get_all_alarms() template = self.response_template(DESCRIBE_ALARMS_TEMPLATE) return template.render(alarms=alarms) def delete_alarms(self): alarm_names = self._get_multi_param('AlarmNames.member') - cloudwatch_backend = cloudwatch_backends[self.region] - cloudwatch_backend.delete_alarms(alarm_names) + self.cloudwatch_backend.delete_alarms(alarm_names) template = self.response_template(DELETE_METRIC_ALARMS_TEMPLATE) return template.render() @@ -89,17 +95,77 @@ class CloudWatchResponse(BaseResponse): dimension_index += 1 metric_data.append([metric_name, value, dimensions]) metric_index += 1 - cloudwatch_backend = cloudwatch_backends[self.region] - cloudwatch_backend.put_metric_data(namespace, metric_data) + self.cloudwatch_backend.put_metric_data(namespace, metric_data) template = self.response_template(PUT_METRIC_DATA_TEMPLATE) return template.render() def list_metrics(self): - cloudwatch_backend = cloudwatch_backends[self.region] - metrics = cloudwatch_backend.get_all_metrics() + metrics = self.cloudwatch_backend.get_all_metrics() template = self.response_template(LIST_METRICS_TEMPLATE) return template.render(metrics=metrics) + def delete_dashboards(self): + dashboards = self._get_multi_param('DashboardNames.member') + if dashboards is None: + return self._error('InvalidParameterValue', 'Need at least 1 dashboard') + + status, error = self.cloudwatch_backend.delete_dashboards(dashboards) + if not status: + return self._error('ResourceNotFound', error) + + template = self.response_template(DELETE_DASHBOARD_TEMPLATE) + return template.render() + + def describe_alarm_history(self): + raise NotImplementedError() + + def describe_alarms_for_metric(self): + raise NotImplementedError() + + def disable_alarm_actions(self): + raise NotImplementedError() + + def enable_alarm_actions(self): + raise NotImplementedError() + + def get_dashboard(self): + dashboard_name = self._get_param('DashboardName') + + dashboard = self.cloudwatch_backend.get_dashboard(dashboard_name) + if dashboard is None: + return self._error('ResourceNotFound', 'Dashboard does not exist') + + template = self.response_template(GET_DASHBOARD_TEMPLATE) + return template.render(dashboard=dashboard) + + def get_metric_statistics(self): + raise NotImplementedError() + + def list_dashboards(self): + prefix = self._get_param('DashboardNamePrefix', '') + + dashboards = self.cloudwatch_backend.list_dashboards(prefix) + + template = self.response_template(LIST_DASHBOARD_RESPONSE) + return template.render(dashboards=dashboards) + + def put_dashboard(self): + name = self._get_param('DashboardName') + body = self._get_param('DashboardBody') + + try: + json.loads(body) + except ValueError: + return self._error('InvalidParameterInput', 'Body is invalid JSON') + + self.cloudwatch_backend.put_dashboard(name, body) + + template = self.response_template(PUT_DASHBOARD_RESPONSE) + return template.render() + + def set_alarm_state(self): + raise NotImplementedError() + PUT_METRIC_ALARM_TEMPLATE = """ @@ -199,3 +265,58 @@ LIST_METRICS_TEMPLATE = """ + + + + + 44b1d4d8-9fa3-11e7-8ad3-41b86ac5e49e + +""" + +LIST_DASHBOARD_RESPONSE = """ + + + {% for dashboard in dashboards %} + + {{ dashboard.arn }} + {{ dashboard.last_modified_iso }} + {{ dashboard.size }} + {{ dashboard.name }} + + {% endfor %} + + + + c3773873-9fa5-11e7-b315-31fcc9275d62 + +""" + +DELETE_DASHBOARD_TEMPLATE = """ + + + 68d1dc8c-9faa-11e7-a694-df2715690df2 + +""" + +GET_DASHBOARD_TEMPLATE = """ + + {{ dashboard.arn }} + {{ dashboard.body }} + {{ dashboard.name }} + + + e3c16bb0-9faa-11e7-b315-31fcc9275d62 + + +""" + +ERROR_RESPONSE_TEMPLATE = """ + + Sender + {{ code }} + {{ message }} + + 5e45fd1e-9fa3-11e7-b720-89e8821d38c4 +""" diff --git a/moto/cloudwatch/utils.py b/moto/cloudwatch/utils.py new file mode 100644 index 000000000..ee33a4402 --- /dev/null +++ b/moto/cloudwatch/utils.py @@ -0,0 +1,5 @@ +from __future__ import unicode_literals + + +def make_arn_for_dashboard(account_id, name): + return "arn:aws:cloudwatch::{0}dashboard/{1}".format(account_id, name) diff --git a/moto/core/responses.py b/moto/core/responses.py index 82e9d4cad..e85054802 100644 --- a/moto/core/responses.py +++ b/moto/core/responses.py @@ -167,7 +167,7 @@ class BaseResponse(_TemplateEnvironmentMixin): match = re.search(self.region_regex, full_url) if match: region = match.group(1) - elif 'Authorization' in request.headers: + elif 'Authorization' in request.headers and 'AWS4' in request.headers['Authorization']: region = request.headers['Authorization'].split(",")[ 0].split("/")[2] else: @@ -178,8 +178,7 @@ class BaseResponse(_TemplateEnvironmentMixin): self.setup_class(request, full_url, headers) return self.call_action() - def call_action(self): - headers = self.response_headers + def _get_action(self): action = self.querystring.get('Action', [""])[0] if not action: # Some services use a header for the action # Headers are case-insensitive. Probably a better way to do this. @@ -188,7 +187,11 @@ class BaseResponse(_TemplateEnvironmentMixin): if match: action = match.split(".")[-1] - action = camelcase_to_underscores(action) + return action + + def call_action(self): + headers = self.response_headers + action = camelcase_to_underscores(self._get_action()) method_names = method_names_from_class(self.__class__) if action in method_names: method = getattr(self, action) @@ -310,7 +313,7 @@ class BaseResponse(_TemplateEnvironmentMixin): param_index += 1 return results - def _get_map_prefix(self, param_prefix): + def _get_map_prefix(self, param_prefix, key_end='.key', value_end='.value'): results = {} param_index = 1 while 1: @@ -319,9 +322,9 @@ class BaseResponse(_TemplateEnvironmentMixin): k, v = None, None for key, value in self.querystring.items(): if key.startswith(index_prefix): - if key.endswith('.key'): + if key.endswith(key_end): k = value[0] - elif key.endswith('.value'): + elif key.endswith(value_end): v = value[0] if not (k and v): diff --git a/moto/dynamodb/responses.py b/moto/dynamodb/responses.py index 0da3e5045..d4f832be2 100644 --- a/moto/dynamodb/responses.py +++ b/moto/dynamodb/responses.py @@ -7,33 +7,6 @@ from moto.core.utils import camelcase_to_underscores from .models import dynamodb_backend, dynamo_json_dump -GET_SESSION_TOKEN_RESULT = """ - - - - - AQoEXAMPLEH4aoAH0gNCAPyJxz4BlCFFxWNE1OPTgk5TthT+FvwqnKwRcOIfrRh3c/L - To6UDdyJwOOvEVPvLXCrrrUtdnniCEXAMPLE/IvU1dYUg2RVAJBanLiHb4IgRmpRV3z - rkuWJOgQs8IZZaIv2BXIa2R4OlgkBN9bkUDNCJiBeb/AXlzBBko7b15fjrBs2+cTQtp - Z3CYWFXG8C5zqx37wnOE49mRl/+OtkIKGO7fAE - - - wJalrXUtnFEMI/K7MDENG/bPxRfiCYzEXAMPLEKEY - - 2011-07-11T19:55:29.611Z - AKIAIOSFODNN7EXAMPLE - - - - 58c5dbae-abef-11e0-8cfe-09039844ac7d - -""" - - -def sts_handler(): - return GET_SESSION_TOKEN_RESULT - - class DynamoHandler(BaseResponse): def get_endpoint_name(self, headers): @@ -51,11 +24,7 @@ class DynamoHandler(BaseResponse): return status, self.response_headers, dynamo_json_dump({'__type': type_}) def call_action(self): - body = self.body - if 'GetSessionToken' in body: - return 200, self.response_headers, sts_handler() - - self.body = json.loads(body or '{}') + self.body = json.loads(self.body or '{}') endpoint = self.get_endpoint_name(self.headers) if endpoint: endpoint = camelcase_to_underscores(endpoint) diff --git a/moto/dynamodb/urls.py b/moto/dynamodb/urls.py index 66c15d022..6988f6e15 100644 --- a/moto/dynamodb/urls.py +++ b/moto/dynamodb/urls.py @@ -2,8 +2,7 @@ from __future__ import unicode_literals from .responses import DynamoHandler url_bases = [ - "https?://dynamodb.(.+).amazonaws.com", - "https?://sts.amazonaws.com", + "https?://dynamodb.(.+).amazonaws.com" ] url_paths = { diff --git a/moto/dynamodb2/models.py b/moto/dynamodb2/models.py index 7590ee1e1..fde269726 100644 --- a/moto/dynamodb2/models.py +++ b/moto/dynamodb2/models.py @@ -57,7 +57,7 @@ class DynamoType(object): @property def cast_value(self): - if self.type == 'N': + if self.is_number(): try: return int(self.value) except ValueError: @@ -76,6 +76,15 @@ class DynamoType(object): comparison_func = get_comparison_func(range_comparison) return comparison_func(self.cast_value, *range_values) + def is_number(self): + return self.type == 'N' + + def is_set(self): + return self.type == 'SS' or self.type == 'NS' or self.type == 'BS' + + def same_type(self, other): + return self.type == other.type + class Item(BaseModel): @@ -118,10 +127,11 @@ class Item(BaseModel): def update(self, update_expression, expression_attribute_names, expression_attribute_values): # Update subexpressions are identifiable by the operator keyword, so split on that and # get rid of the empty leading string. - parts = [p for p in re.split(r'\b(SET|REMOVE|ADD|DELETE)\b', update_expression) if p] + parts = [p for p in re.split(r'\b(SET|REMOVE|ADD|DELETE)\b', update_expression, flags=re.I) if p] # make sure that we correctly found only operator/value pairs assert len(parts) % 2 == 0, "Mismatched operators and values in update expression: '{}'".format(update_expression) for action, valstr in zip(parts[:-1:2], parts[1::2]): + action = action.upper() values = valstr.split(',') for value in values: # A Real value @@ -139,6 +149,55 @@ class Item(BaseModel): self.attrs[key] = DynamoType(expression_attribute_values[value]) else: self.attrs[key] = DynamoType({"S": value}) + elif action == 'ADD': + key, value = value.split(" ", 1) + key = key.strip() + value_str = value.strip() + if value_str in expression_attribute_values: + dyn_value = DynamoType(expression_attribute_values[value]) + else: + raise TypeError + + # Handle adding numbers - value gets added to existing value, + # or added to 0 if it doesn't exist yet + if dyn_value.is_number(): + existing = self.attrs.get(key, DynamoType({"N": '0'})) + if not existing.same_type(dyn_value): + raise TypeError() + self.attrs[key] = DynamoType({"N": str( + decimal.Decimal(existing.value) + + decimal.Decimal(dyn_value.value) + )}) + + # Handle adding sets - value is added to the set, or set is + # created with only this value if it doesn't exist yet + # New value must be of same set type as previous value + elif dyn_value.is_set(): + existing = self.attrs.get(key, DynamoType({dyn_value.type: {}})) + if not existing.same_type(dyn_value): + raise TypeError() + new_set = set(existing.value).union(dyn_value.value) + self.attrs[key] = DynamoType({existing.type: list(new_set)}) + else: # Number and Sets are the only supported types for ADD + raise TypeError + + elif action == 'DELETE': + key, value = value.split(" ", 1) + key = key.strip() + value_str = value.strip() + if value_str in expression_attribute_values: + dyn_value = DynamoType(expression_attribute_values[value]) + else: + raise TypeError + + if not dyn_value.is_set(): + raise TypeError + existing = self.attrs.get(key, None) + if existing: + if not existing.same_type(dyn_value): + raise TypeError + new_set = set(existing.value).difference(dyn_value.value) + self.attrs[key] = DynamoType({existing.type: list(new_set)}) else: raise NotImplementedError('{} update action not yet supported'.format(action)) @@ -171,6 +230,12 @@ class Item(BaseModel): decimal.Decimal(existing.value) + decimal.Decimal(new_value) )}) + elif set(update_action['Value'].keys()) == set(['SS']): + existing = self.attrs.get(attribute_name, DynamoType({"SS": {}})) + new_set = set(existing.value).union(set(new_value)) + self.attrs[attribute_name] = DynamoType({ + "SS": list(new_set) + }) else: # TODO: implement other data types raise NotImplementedError( @@ -347,7 +412,8 @@ class Table(BaseModel): return None def query(self, hash_key, range_comparison, range_objs, limit, - exclusive_start_key, scan_index_forward, index_name=None, **filter_kwargs): + exclusive_start_key, scan_index_forward, projection_expression, + index_name=None, **filter_kwargs): results = [] if index_name: all_indexes = (self.global_indexes or []) + (self.indexes or []) @@ -418,6 +484,13 @@ class Table(BaseModel): else: results.sort(key=lambda item: item.range_key) + if projection_expression: + expressions = [x.strip() for x in projection_expression.split(',')] + for result in possible_results: + for attr in list(result.attrs): + if attr not in expressions: + result.attrs.pop(attr) + if scan_index_forward is False: results.reverse() @@ -613,7 +686,7 @@ class DynamoDBBackend(BaseBackend): return table.get_item(hash_key, range_key) def query(self, table_name, hash_key_dict, range_comparison, range_value_dicts, - limit, exclusive_start_key, scan_index_forward, index_name=None, **filter_kwargs): + limit, exclusive_start_key, scan_index_forward, projection_expression, index_name=None, **filter_kwargs): table = self.tables.get(table_name) if not table: return None, None @@ -623,7 +696,7 @@ class DynamoDBBackend(BaseBackend): for range_value in range_value_dicts] return table.query(hash_key, range_comparison, range_values, limit, - exclusive_start_key, scan_index_forward, index_name, **filter_kwargs) + exclusive_start_key, scan_index_forward, projection_expression, index_name, **filter_kwargs) def scan(self, table_name, filters, limit, exclusive_start_key): table = self.tables.get(table_name) diff --git a/moto/dynamodb2/responses.py b/moto/dynamodb2/responses.py index bf24e9964..437850713 100644 --- a/moto/dynamodb2/responses.py +++ b/moto/dynamodb2/responses.py @@ -8,33 +8,6 @@ from moto.core.utils import camelcase_to_underscores from .models import dynamodb_backend2, dynamo_json_dump -GET_SESSION_TOKEN_RESULT = """ - - - - - AQoEXAMPLEH4aoAH0gNCAPyJxz4BlCFFxWNE1OPTgk5TthT+FvwqnKwRcOIfrRh3c/L - To6UDdyJwOOvEVPvLXCrrrUtdnniCEXAMPLE/IvU1dYUg2RVAJBanLiHb4IgRmpRV3z - rkuWJOgQs8IZZaIv2BXIa2R4OlgkBN9bkUDNCJiBeb/AXlzBBko7b15fjrBs2+cTQtp - Z3CYWFXG8C5zqx37wnOE49mRl/+OtkIKGO7fAE - - - wJalrXUtnFEMI/K7MDENG/bPxRfiCYzEXAMPLEKEY - - 2011-07-11T19:55:29.611Z - AKIAIOSFODNN7EXAMPLE - - - - 58c5dbae-abef-11e0-8cfe-09039844ac7d - -""" - - -def sts_handler(): - return GET_SESSION_TOKEN_RESULT - - class DynamoHandler(BaseResponse): def get_endpoint_name(self, headers): @@ -48,15 +21,11 @@ class DynamoHandler(BaseResponse): if match: return match.split(".")[1] - def error(self, type_, status=400): - return status, self.response_headers, dynamo_json_dump({'__type': type_}) + def error(self, type_, message, status=400): + return status, self.response_headers, dynamo_json_dump({'__type': type_, 'message': message}) def call_action(self): - body = self.body - if 'GetSessionToken' in body: - return 200, self.response_headers, sts_handler() - - self.body = json.loads(body or '{}') + self.body = json.loads(self.body or '{}') endpoint = self.get_endpoint_name(self.headers) if endpoint: endpoint = camelcase_to_underscores(endpoint) @@ -113,7 +82,7 @@ class DynamoHandler(BaseResponse): return dynamo_json_dump(table.describe()) else: er = 'com.amazonaws.dynamodb.v20111205#ResourceInUseException' - return self.error(er) + return self.error(er, 'Resource in use') def delete_table(self): name = self.body['TableName'] @@ -122,7 +91,7 @@ class DynamoHandler(BaseResponse): return dynamo_json_dump(table.describe()) else: er = 'com.amazonaws.dynamodb.v20111205#ResourceNotFoundException' - return self.error(er) + return self.error(er, 'Requested resource not found') def tag_resource(self): tags = self.body['Tags'] @@ -151,7 +120,7 @@ class DynamoHandler(BaseResponse): return json.dumps({'Tags': tags_resp}) except AttributeError: er = 'com.amazonaws.dynamodb.v20111205#ResourceNotFoundException' - return self.error(er) + return self.error(er, 'Requested resource not found') def update_table(self): name = self.body['TableName'] @@ -169,12 +138,24 @@ class DynamoHandler(BaseResponse): table = dynamodb_backend2.tables[name] except KeyError: er = 'com.amazonaws.dynamodb.v20111205#ResourceNotFoundException' - return self.error(er) + return self.error(er, 'Requested resource not found') return dynamo_json_dump(table.describe(base_key='Table')) def put_item(self): name = self.body['TableName'] item = self.body['Item'] + + res = re.search('\"\"', json.dumps(item)) + if res: + er = 'com.amazonaws.dynamodb.v20111205#ValidationException' + return (400, + {'server': 'amazon.com'}, + dynamo_json_dump({'__type': er, + 'message': ('One or more parameter values were ' + 'invalid: An AttributeValue may not ' + 'contain an empty string')} + )) + overwrite = 'Expected' not in self.body if not overwrite: expected = self.body['Expected'] @@ -209,15 +190,18 @@ class DynamoHandler(BaseResponse): name, item, expected, overwrite) except ValueError: er = 'com.amazonaws.dynamodb.v20111205#ConditionalCheckFailedException' - return self.error(er) + return self.error(er, 'A condition specified in the operation could not be evaluated.') if result: item_dict = result.to_json() - item_dict['ConsumedCapacityUnits'] = 1 + item_dict['ConsumedCapacity'] = { + 'TableName': name, + 'CapacityUnits': 1 + } return dynamo_json_dump(item_dict) else: er = 'com.amazonaws.dynamodb.v20111205#ResourceNotFoundException' - return self.error(er) + return self.error(er, 'Requested resource not found') def batch_write_item(self): table_batches = self.body['RequestItems'] @@ -254,15 +238,17 @@ class DynamoHandler(BaseResponse): item = dynamodb_backend2.get_item(name, key) except ValueError: er = 'com.amazon.coral.validate#ValidationException' - return self.error(er, status=400) + return self.error(er, 'Validation Exception') if item: item_dict = item.describe_attrs(attributes=None) - item_dict['ConsumedCapacityUnits'] = 0.5 + item_dict['ConsumedCapacity'] = { + 'TableName': name, + 'CapacityUnits': 0.5 + } return dynamo_json_dump(item_dict) else: # Item not found - er = '{}' - return self.error(er, status=200) + return 200, self.response_headers, '{}' def batch_get_item(self): table_batches = self.body['RequestItems'] @@ -296,11 +282,26 @@ class DynamoHandler(BaseResponse): name = self.body['TableName'] # {u'KeyConditionExpression': u'#n0 = :v0', u'ExpressionAttributeValues': {u':v0': {u'S': u'johndoe'}}, u'ExpressionAttributeNames': {u'#n0': u'username'}} key_condition_expression = self.body.get('KeyConditionExpression') + projection_expression = self.body.get('ProjectionExpression') + expression_attribute_names = self.body.get('ExpressionAttributeNames') + + if projection_expression and expression_attribute_names: + expressions = [x.strip() for x in projection_expression.split(',')] + for expression in expressions: + if expression in expression_attribute_names: + projection_expression = projection_expression.replace(expression, expression_attribute_names[expression]) + filter_kwargs = {} if key_condition_expression: value_alias_map = self.body['ExpressionAttributeValues'] table = dynamodb_backend2.get_table(name) + + # If table does not exist + if table is None: + return self.error('com.amazonaws.dynamodb.v20120810#ResourceNotFoundException', + 'Requested resource not found') + index_name = self.body.get('IndexName') if index_name: all_indexes = (table.global_indexes or []) + \ @@ -369,7 +370,7 @@ class DynamoHandler(BaseResponse): filter_kwargs[key] = value if hash_key_name is None: er = "'com.amazonaws.dynamodb.v20120810#ResourceNotFoundException" - return self.error(er) + return self.error(er, 'Requested resource not found') hash_key = key_conditions[hash_key_name][ 'AttributeValueList'][0] if len(key_conditions) == 1: @@ -378,7 +379,7 @@ class DynamoHandler(BaseResponse): else: if range_key_name is None and not filter_kwargs: er = "com.amazon.coral.validate#ValidationException" - return self.error(er) + return self.error(er, 'Validation Exception') else: range_condition = key_conditions.get(range_key_name) if range_condition: @@ -397,16 +398,20 @@ class DynamoHandler(BaseResponse): scan_index_forward = self.body.get("ScanIndexForward") items, scanned_count, last_evaluated_key = dynamodb_backend2.query( name, hash_key, range_comparison, range_values, limit, - exclusive_start_key, scan_index_forward, index_name=index_name, **filter_kwargs) + exclusive_start_key, scan_index_forward, projection_expression, index_name=index_name, **filter_kwargs) if items is None: er = 'com.amazonaws.dynamodb.v20111205#ResourceNotFoundException' - return self.error(er) + return self.error(er, 'Requested resource not found') result = { "Count": len(items), - "ConsumedCapacityUnits": 1, + 'ConsumedCapacity': { + 'TableName': name, + 'CapacityUnits': 1, + }, "ScannedCount": scanned_count } + if self.body.get('Select', '').upper() != 'COUNT': result["Items"] = [item.attrs for item in items] @@ -436,12 +441,15 @@ class DynamoHandler(BaseResponse): if items is None: er = 'com.amazonaws.dynamodb.v20111205#ResourceNotFoundException' - return self.error(er) + return self.error(er, 'Requested resource not found') result = { "Count": len(items), "Items": [item.attrs for item in items], - "ConsumedCapacityUnits": 1, + 'ConsumedCapacity': { + 'TableName': name, + 'CapacityUnits': 1, + }, "ScannedCount": scanned_count } if last_evaluated_key is not None: @@ -455,7 +463,7 @@ class DynamoHandler(BaseResponse): table = dynamodb_backend2.get_table(name) if not table: er = 'com.amazonaws.dynamodb.v20120810#ConditionalCheckFailedException' - return self.error(er) + return self.error(er, 'A condition specified in the operation could not be evaluated.') item = dynamodb_backend2.delete_item(name, keys) if item and return_values == 'ALL_OLD': @@ -515,10 +523,16 @@ class DynamoHandler(BaseResponse): expected) except ValueError: er = 'com.amazonaws.dynamodb.v20111205#ConditionalCheckFailedException' - return self.error(er) + return self.error(er, 'A condition specified in the operation could not be evaluated.') + except TypeError: + er = 'com.amazonaws.dynamodb.v20111205#ValidationException' + return self.error(er, 'Validation Exception') item_dict = item.to_json() - item_dict['ConsumedCapacityUnits'] = 0.5 + item_dict['ConsumedCapacity'] = { + 'TableName': name, + 'CapacityUnits': 0.5 + } if not existing_item: item_dict['Attributes'] = {} diff --git a/moto/dynamodb2/urls.py b/moto/dynamodb2/urls.py index 66c15d022..6988f6e15 100644 --- a/moto/dynamodb2/urls.py +++ b/moto/dynamodb2/urls.py @@ -2,8 +2,7 @@ from __future__ import unicode_literals from .responses import DynamoHandler url_bases = [ - "https?://dynamodb.(.+).amazonaws.com", - "https?://sts.amazonaws.com", + "https?://dynamodb.(.+).amazonaws.com" ] url_paths = { diff --git a/moto/ec2/exceptions.py b/moto/ec2/exceptions.py index e5432baf7..5cff527be 100644 --- a/moto/ec2/exceptions.py +++ b/moto/ec2/exceptions.py @@ -375,3 +375,20 @@ class RulesPerSecurityGroupLimitExceededError(EC2ClientError): "RulesPerSecurityGroupLimitExceeded", 'The maximum number of rules per security group ' 'has been reached.') + + +class MotoNotImplementedError(NotImplementedError): + + def __init__(self, blurb): + super(MotoNotImplementedError, self).__init__( + "{0} has not been implemented in Moto yet." + " Feel free to open an issue at" + " https://github.com/spulec/moto/issues".format(blurb)) + + +class FilterNotImplementedError(MotoNotImplementedError): + + def __init__(self, filter_name, method_name): + super(FilterNotImplementedError, self).__init__( + "The filter '{0}' for {1}".format( + filter_name, method_name)) diff --git a/moto/ec2/models.py b/moto/ec2/models.py index 6c35093e2..10fec7fd7 100755 --- a/moto/ec2/models.py +++ b/moto/ec2/models.py @@ -2,6 +2,8 @@ from __future__ import unicode_literals import copy import itertools +import json +import os import re import six @@ -62,6 +64,8 @@ from .exceptions import ( InvalidVpnConnectionIdError, InvalidCustomerGatewayIdError, RulesPerSecurityGroupLimitExceededError, + MotoNotImplementedError, + FilterNotImplementedError ) from .utils import ( EC2_RESOURCE_TO_PREFIX, @@ -107,6 +111,9 @@ from .utils import ( is_tag_filter, ) +RESOURCES_DIR = os.path.join(os.path.dirname(__file__), 'resources') +INSTANCE_TYPES = json.load(open(os.path.join(RESOURCES_DIR, 'instance_types.json'), 'r')) + def utc_date_and_time(): return datetime.utcnow().strftime('%Y-%m-%dT%H:%M:%S.000Z') @@ -144,7 +151,7 @@ class TaggedEC2Resource(BaseModel): for key, value in tag_map.items(): self.ec2_backend.create_tags([self.id], {key: value}) - def get_filter_value(self, filter_name): + def get_filter_value(self, filter_name, method_name=None): tags = self.get_tags() if filter_name.startswith('tag:'): @@ -154,12 +161,12 @@ class TaggedEC2Resource(BaseModel): return tag['value'] return '' - - if filter_name == 'tag-key': + elif filter_name == 'tag-key': return [tag['key'] for tag in tags] - - if filter_name == 'tag-value': + elif filter_name == 'tag-value': return [tag['value'] for tag in tags] + else: + raise FilterNotImplementedError(filter_name, method_name) class NetworkInterface(TaggedEC2Resource): @@ -261,17 +268,9 @@ class NetworkInterface(TaggedEC2Resource): return [group.id for group in self._group_set] elif filter_name == 'availability-zone': return self.subnet.availability_zone - - filter_value = super( - NetworkInterface, self).get_filter_value(filter_name) - - if filter_value is None: - self.ec2_backend.raise_not_implemented_error( - "The filter '{0}' for DescribeNetworkInterfaces".format( - filter_name) - ) - - return filter_value + else: + return super(NetworkInterface, self).get_filter_value( + filter_name, 'DescribeNetworkInterfaces') class NetworkInterfaceBackend(object): @@ -366,6 +365,7 @@ class Instance(TaggedEC2Resource, BotoInstance): self.user_data = user_data self.security_groups = security_groups self.instance_type = kwargs.get("instance_type", "m1.small") + self.region_name = kwargs.get("region_name", "us-east-1") placement = kwargs.get("placement", None) self.vpc_id = None self.subnet_id = kwargs.get("subnet_id") @@ -433,7 +433,11 @@ class Instance(TaggedEC2Resource, BotoInstance): @property def private_dns(self): - return "ip-{0}.ec2.internal".format(self.private_ip) + formatted_ip = self.private_ip.replace('.', '-') + if self.region_name == "us-east-1": + return "ip-{0}.ec2.internal".format(formatted_ip) + else: + return "ip-{0}.{1}.compute.internal".format(formatted_ip, self.region_name) @property def public_ip(self): @@ -442,7 +446,11 @@ class Instance(TaggedEC2Resource, BotoInstance): @property def public_dns(self): if self.public_ip: - return "ec2-{0}.compute-1.amazonaws.com".format(self.public_ip) + formatted_ip = self.public_ip.replace('.', '-') + if self.region_name == "us-east-1": + return "ec2-{0}.compute-1.amazonaws.com".format(formatted_ip) + else: + return "ec2-{0}.{1}.compute.amazonaws.com".format(formatted_ip, self.region_name) @classmethod def create_from_cloudformation_json(cls, resource_name, cloudformation_json, region_name): @@ -581,10 +589,6 @@ class Instance(TaggedEC2Resource, BotoInstance): self.attach_eni(use_nic, device_index) - def set_ip(self, ip_address): - # Should we be creating a new ENI? - self.nics[0].public_ip = ip_address - def attach_eni(self, eni, device_index): device_index = int(device_index) self.nics[device_index] = eni @@ -786,16 +790,31 @@ class InstanceBackend(object): return reservations +class KeyPair(object): + def __init__(self, name, fingerprint, material): + self.name = name + self.fingerprint = fingerprint + self.material = material + + def get_filter_value(self, filter_name): + if filter_name == 'key-name': + return self.name + elif filter_name == 'fingerprint': + return self.fingerprint + else: + raise FilterNotImplementedError(filter_name, 'DescribeKeyPairs') + + class KeyPairBackend(object): def __init__(self): - self.keypairs = defaultdict(dict) + self.keypairs = {} super(KeyPairBackend, self).__init__() def create_key_pair(self, name): if name in self.keypairs: raise InvalidKeyPairDuplicateError(name) - self.keypairs[name] = keypair = random_key_pair() - keypair['name'] = name + keypair = KeyPair(name, **random_key_pair()) + self.keypairs[name] = keypair return keypair def delete_key_pair(self, name): @@ -803,24 +822,27 @@ class KeyPairBackend(object): self.keypairs.pop(name) return True - def describe_key_pairs(self, filter_names=None): + def describe_key_pairs(self, key_names=None, filters=None): results = [] - for name, keypair in self.keypairs.items(): - if not filter_names or name in filter_names: - keypair['name'] = name - results.append(keypair) + if key_names: + results = [keypair for keypair in self.keypairs.values() + if keypair.name in key_names] + if len(key_names) > len(results): + unknown_keys = set(key_names) - set(results) + raise InvalidKeyPairNameError(unknown_keys) + else: + results = self.keypairs.values() - # TODO: Trim error message down to specific invalid name. - if filter_names and len(filter_names) > len(results): - raise InvalidKeyPairNameError(filter_names) - - return results + if filters: + return generic_filter(filters, results) + else: + return results def import_key_pair(self, key_name, public_key_material): if key_name in self.keypairs: raise InvalidKeyPairDuplicateError(key_name) - self.keypairs[key_name] = keypair = random_key_pair() - keypair['name'] = key_name + keypair = KeyPair(key_name, **random_key_pair()) + self.keypairs[key_name] = keypair return keypair @@ -1018,14 +1040,9 @@ class Ami(TaggedEC2Resource): return self.state elif filter_name == 'name': return self.name - - filter_value = super(Ami, self).get_filter_value(filter_name) - - if filter_value is None: - self.ec2_backend.raise_not_implemented_error( - "The filter '{0}' for DescribeImages".format(filter_name)) - - return filter_value + else: + return super(Ami, self).get_filter_value( + filter_name, 'DescribeImages') class AmiBackend(object): @@ -1348,22 +1365,25 @@ class SecurityGroupBackend(object): return group def describe_security_groups(self, group_ids=None, groupnames=None, filters=None): - all_groups = itertools.chain(*[x.values() - for x in self.groups.values()]) - groups = [] + matches = itertools.chain(*[x.values() + for x in self.groups.values()]) + if group_ids: + matches = [grp for grp in matches + if grp.id in group_ids] + if len(group_ids) > len(matches): + unknown_ids = set(group_ids) - set(matches) + raise InvalidSecurityGroupNotFoundError(unknown_ids) + if groupnames: + matches = [grp for grp in matches + if grp.name in groupnames] + if len(groupnames) > len(matches): + unknown_names = set(groupnames) - set(matches) + raise InvalidSecurityGroupNotFoundError(unknown_names) + if filters: + matches = [grp for grp in matches + if grp.matches_filters(filters)] - if group_ids or groupnames or filters: - for group in all_groups: - if ((group_ids and group.id not in group_ids) or - (groupnames and group.name not in groupnames)): - continue - if filters and not group.matches_filters(filters): - continue - groups.append(group) - else: - groups = all_groups - - return groups + return matches def _delete_security_group(self, vpc_id, group_id): if self.groups[vpc_id][group_id].enis: @@ -1682,43 +1702,31 @@ class Volume(TaggedEC2Resource): return 'available' def get_filter_value(self, filter_name): - if filter_name.startswith('attachment') and not self.attachment: return None - if filter_name == 'attachment.attach-time': + elif filter_name == 'attachment.attach-time': return self.attachment.attach_time - if filter_name == 'attachment.device': + elif filter_name == 'attachment.device': return self.attachment.device - if filter_name == 'attachment.instance-id': + elif filter_name == 'attachment.instance-id': return self.attachment.instance.id - if filter_name == 'attachment.status': + elif filter_name == 'attachment.status': return self.attachment.status - - if filter_name == 'create-time': + elif filter_name == 'create-time': return self.create_time - - if filter_name == 'size': + elif filter_name == 'size': return self.size - - if filter_name == 'snapshot-id': + elif filter_name == 'snapshot-id': return self.snapshot_id - - if filter_name == 'status': + elif filter_name == 'status': return self.status - - if filter_name == 'volume-id': + elif filter_name == 'volume-id': return self.id - - if filter_name == 'encrypted': + elif filter_name == 'encrypted': return str(self.encrypted).lower() - - filter_value = super(Volume, self).get_filter_value(filter_name) - - if filter_value is None: - self.ec2_backend.raise_not_implemented_error( - "The filter '{0}' for DescribeVolumes".format(filter_name)) - - return filter_value + else: + return super(Volume, self).get_filter_value( + filter_name, 'DescribeVolumes') class Snapshot(TaggedEC2Resource): @@ -1733,35 +1741,23 @@ class Snapshot(TaggedEC2Resource): self.encrypted = encrypted def get_filter_value(self, filter_name): - if filter_name == 'description': return self.description - - if filter_name == 'snapshot-id': + elif filter_name == 'snapshot-id': return self.id - - if filter_name == 'start-time': + elif filter_name == 'start-time': return self.start_time - - if filter_name == 'volume-id': + elif filter_name == 'volume-id': return self.volume.id - - if filter_name == 'volume-size': + elif filter_name == 'volume-size': return self.volume.size - - if filter_name == 'encrypted': + elif filter_name == 'encrypted': return str(self.encrypted).lower() - - if filter_name == 'status': + elif filter_name == 'status': return self.status - - filter_value = super(Snapshot, self).get_filter_value(filter_name) - - if filter_value is None: - self.ec2_backend.raise_not_implemented_error( - "The filter '{0}' for DescribeSnapshots".format(filter_name)) - - return filter_value + else: + return super(Snapshot, self).get_filter_value( + filter_name, 'DescribeSnapshots') class EBSBackend(object): @@ -1784,11 +1780,17 @@ class EBSBackend(object): self.volumes[volume_id] = volume return volume - def describe_volumes(self, filters=None): + def describe_volumes(self, volume_ids=None, filters=None): + matches = self.volumes.values() + if volume_ids: + matches = [vol for vol in matches + if vol.id in volume_ids] + if len(volume_ids) > len(matches): + unknown_ids = set(volume_ids) - set(matches) + raise InvalidVolumeIdError(unknown_ids) if filters: - volumes = self.volumes.values() - return generic_filter(filters, volumes) - return self.volumes.values() + matches = generic_filter(filters, matches) + return matches def get_volume(self, volume_id): volume = self.volumes.get(volume_id, None) @@ -1836,11 +1838,17 @@ class EBSBackend(object): self.snapshots[snapshot_id] = snapshot return snapshot - def describe_snapshots(self, filters=None): + def describe_snapshots(self, snapshot_ids=None, filters=None): + matches = self.snapshots.values() + if snapshot_ids: + matches = [snap for snap in matches + if snap.id in snapshot_ids] + if len(snapshot_ids) > len(matches): + unknown_ids = set(snapshot_ids) - set(matches) + raise InvalidSnapshotIdError(unknown_ids) if filters: - snapshots = self.snapshots.values() - return generic_filter(filters, snapshots) - return self.snapshots.values() + matches = generic_filter(filters, matches) + return matches def get_snapshot(self, snapshot_id): snapshot = self.snapshots.get(snapshot_id, None) @@ -1923,16 +1931,10 @@ class VPC(TaggedEC2Resource): elif filter_name in ('dhcp-options-id', 'dhcpOptionsId'): if not self.dhcp_options: return None - return self.dhcp_options.id - - filter_value = super(VPC, self).get_filter_value(filter_name) - - if filter_value is None: - self.ec2_backend.raise_not_implemented_error( - "The filter '{0}' for DescribeVPCs".format(filter_name)) - - return filter_value + else: + return super(VPC, self).get_filter_value( + filter_name, 'DescribeVpcs') class VPCBackend(object): @@ -1965,12 +1967,16 @@ class VPCBackend(object): return self.vpcs.get(vpc_id) def get_all_vpcs(self, vpc_ids=None, filters=None): + matches = self.vpcs.values() if vpc_ids: - vpcs = [vpc for vpc in self.vpcs.values() if vpc.id in vpc_ids] - else: - vpcs = self.vpcs.values() - - return generic_filter(filters, vpcs) + matches = [vpc for vpc in matches + if vpc.id in vpc_ids] + if len(vpc_ids) > len(matches): + unknown_ids = set(vpc_ids) - set(matches) + raise InvalidVPCIdError(unknown_ids) + if filters: + matches = generic_filter(filters, matches) + return matches def delete_vpc(self, vpc_id): # Delete route table if only main route table remains. @@ -2166,14 +2172,9 @@ class Subnet(TaggedEC2Resource): return self.availability_zone elif filter_name in ('defaultForAz', 'default-for-az'): return self.default_for_az - - filter_value = super(Subnet, self).get_filter_value(filter_name) - - if filter_value is None: - self.ec2_backend.raise_not_implemented_error( - "The filter '{0}' for DescribeSubnets".format(filter_name)) - - return filter_value + else: + return super(Subnet, self).get_filter_value( + filter_name, 'DescribeSubnets') def get_cfn_attribute(self, attribute_name): from moto.cloudformation.exceptions import UnformattedGetAttTemplateException @@ -2212,16 +2213,19 @@ class SubnetBackend(object): return subnet def get_all_subnets(self, subnet_ids=None, filters=None): - subnets = [] + # Extract a list of all subnets + matches = itertools.chain(*[x.values() + for x in self.subnets.values()]) if subnet_ids: - for subnet_id in subnet_ids: - for items in self.subnets.values(): - if subnet_id in items: - subnets.append(items[subnet_id]) - else: - for items in self.subnets.values(): - subnets.extend(items.values()) - return generic_filter(filters, subnets) + matches = [sn for sn in matches + if sn.id in subnet_ids] + if len(subnet_ids) > len(matches): + unknown_ids = set(subnet_ids) - set(matches) + raise InvalidSubnetIdError(unknown_ids) + if filters: + matches = generic_filter(filters, matches) + + return matches def delete_subnet(self, subnet_id): for subnets in self.subnets.values(): @@ -2311,14 +2315,9 @@ class RouteTable(TaggedEC2Resource): return self.associations.keys() elif filter_name == "association.subnet-id": return self.associations.values() - - filter_value = super(RouteTable, self).get_filter_value(filter_name) - - if filter_value is None: - self.ec2_backend.raise_not_implemented_error( - "The filter '{0}' for DescribeRouteTables".format(filter_name)) - - return filter_value + else: + return super(RouteTable, self).get_filter_value( + filter_name, 'DescribeRouteTables') class RouteTableBackend(object): @@ -2665,16 +2664,11 @@ class SpotInstanceRequest(BotoSpotRequest, TaggedEC2Resource): def get_filter_value(self, filter_name): if filter_name == 'state': return self.state - if filter_name == 'spot-instance-request-id': + elif filter_name == 'spot-instance-request-id': return self.id - filter_value = super(SpotInstanceRequest, - self).get_filter_value(filter_name) - - if filter_value is None: - self.ec2_backend.raise_not_implemented_error( - "The filter '{0}' for DescribeSpotInstanceRequests".format(filter_name)) - - return filter_value + else: + return super(SpotInstanceRequest, self).get_filter_value( + filter_name, 'DescribeSpotInstanceRequests') def launch_instance(self): reservation = self.ec2_backend.add_instances( @@ -2934,6 +2928,25 @@ class ElasticAddress(object): return self.allocation_id raise UnformattedGetAttTemplateException() + def get_filter_value(self, filter_name): + if filter_name == 'allocation-id': + return self.allocation_id + elif filter_name == 'association-id': + return self.association_id + elif filter_name == 'domain': + return self.domain + elif filter_name == 'instance-id' and self.instance: + return self.instance.id + elif filter_name == 'network-interface-id' and self.eni: + return self.eni.id + elif filter_name == 'private-ip-address' and self.eni: + return self.eni.private_ip_address + elif filter_name == 'public-ip': + return self.public_ip + else: + # TODO: implement network-interface-owner-id + raise FilterNotImplementedError(filter_name, 'DescribeAddresses') + class ElasticAddressBackend(object): def __init__(self): @@ -2994,19 +3007,36 @@ class ElasticAddressBackend(object): if new_instance_association or new_eni_association or reassociate: eip.instance = instance eip.eni = eni + if not eip.eni and instance: + # default to primary network interface + eip.eni = instance.nics[0] if eip.eni: eip.eni.public_ip = eip.public_ip if eip.domain == "vpc": eip.association_id = random_eip_association_id() - if instance: - instance.set_ip(eip.public_ip) return eip raise ResourceAlreadyAssociatedError(eip.public_ip) - def describe_addresses(self): - return self.addresses + def describe_addresses(self, allocation_ids=None, public_ips=None, filters=None): + matches = self.addresses + if allocation_ids: + matches = [addr for addr in matches + if addr.allocation_id in allocation_ids] + if len(allocation_ids) > len(matches): + unknown_ids = set(allocation_ids) - set(matches) + raise InvalidAllocationIdError(unknown_ids) + if public_ips: + matches = [addr for addr in matches + if addr.public_ip in public_ips] + if len(public_ips) > len(matches): + unknown_ips = set(allocation_ids) - set(matches) + raise InvalidAddressError(unknown_ips) + if filters: + matches = generic_filter(filters, matches) + + return matches def disassociate_address(self, address=None, association_id=None): eips = [] @@ -3017,10 +3047,9 @@ class ElasticAddressBackend(object): eip = eips[0] if eip.eni: + eip.eni.public_ip = None if eip.eni.instance and eip.eni.instance._state.name == "running": eip.eni.check_auto_public_ip() - else: - eip.eni.public_ip = None eip.eni = None eip.instance = None @@ -3076,15 +3105,9 @@ class DHCPOptionsSet(TaggedEC2Resource): elif filter_name == 'value': values = [item for item in list(self._options.values()) if item] return itertools.chain(*values) - - filter_value = super( - DHCPOptionsSet, self).get_filter_value(filter_name) - - if filter_value is None: - self.ec2_backend.raise_not_implemented_error( - "The filter '{0}' for DescribeDhcpOptions".format(filter_name)) - - return filter_value + else: + return super(DHCPOptionsSet, self).get_filter_value( + filter_name, 'DescribeDhcpOptions') @property def options(self): @@ -3171,6 +3194,10 @@ class VPNConnection(TaggedEC2Resource): self.options = None self.static_routes = None + def get_filter_value(self, filter_name): + return super(VPNConnection, self).get_filter_value( + filter_name, 'DescribeVpnConnections') + class VPNConnectionBackend(object): def __init__(self): @@ -3350,14 +3377,9 @@ class NetworkAcl(TaggedEC2Resource): return self.id elif filter_name == "association.subnet-id": return [assoc.subnet_id for assoc in self.associations.values()] - - filter_value = super(NetworkAcl, self).get_filter_value(filter_name) - - if filter_value is None: - self.ec2_backend.raise_not_implemented_error( - "The filter '{0}' for DescribeNetworkAcls".format(filter_name)) - - return filter_value + else: + return super(NetworkAcl, self).get_filter_value( + filter_name, 'DescribeNetworkAcls') class NetworkAclEntry(TaggedEC2Resource): @@ -3386,6 +3408,10 @@ class VpnGateway(TaggedEC2Resource): self.attachments = {} super(VpnGateway, self).__init__() + def get_filter_value(self, filter_name): + return super(VpnGateway, self).get_filter_value( + filter_name, 'DescribeVpnGateways') + class VpnGatewayAttachment(object): def __init__(self, vpc_id, state): @@ -3447,6 +3473,10 @@ class CustomerGateway(TaggedEC2Resource): self.attachments = {} super(CustomerGateway, self).__init__() + def get_filter_value(self, filter_name): + return super(CustomerGateway, self).get_filter_value( + filter_name, 'DescribeCustomerGateways') + class CustomerGatewayBackend(object): def __init__(self): @@ -3590,10 +3620,7 @@ class EC2Backend(BaseBackend, InstanceBackend, TagBackend, AmiBackend, raise EC2ClientError(code, message) def raise_not_implemented_error(self, blurb): - msg = "{0} has not been implemented in Moto yet." \ - " Feel free to open an issue at" \ - " https://github.com/spulec/moto/issues".format(blurb) - raise NotImplementedError(msg) + raise MotoNotImplementedError(blurb) def do_resources_exist(self, resource_ids): for resource_id in resource_ids: @@ -3640,6 +3667,5 @@ class EC2Backend(BaseBackend, InstanceBackend, TagBackend, AmiBackend, return True -ec2_backends = {} -for region in RegionsAndZonesBackend.regions: - ec2_backends[region.name] = EC2Backend(region.name) +ec2_backends = {region.name: EC2Backend(region.name) + for region in RegionsAndZonesBackend.regions} diff --git a/moto/ec2/resources/instance_types.json b/moto/ec2/resources/instance_types.json new file mode 100644 index 000000000..2fa2e4e93 --- /dev/null +++ b/moto/ec2/resources/instance_types.json @@ -0,0 +1 @@ +{"m1.xlarge": {"ecu_per_vcpu": 2.0, "network_perf": 9.0, "intel_avx": "", "name": "M1 General Purpose Extra Large", "architecture": "64-bit", "linux_virtualization": "PV", "storage": 1680.0, "placement_group_support": false, "intel_avx2": "", "clock_speed_ghz": "", "apiname": "m1.xlarge", "computeunits": 8.0, "ebs_throughput": 125.0, "vpc_only": false, "max_ips": 60, "physical_processor": "", "fpga": 0, "intel_turbo": "", "enhanced_networking": false, "ebs_iops": 8000.0, "vcpus": 4.0, "memory": 15.0, "ebs_max_bandwidth": 1000.0, "gpus": 0, "ipv6_support": false}, "i3.4xlarge": {"ecu_per_vcpu": 3.3125, "network_perf": 11.0, "intel_avx": "Yes", "name": "I3 High I/O Quadruple Extra Large", "architecture": "64-bit", "linux_virtualization": "Unknown", "storage": 3800.0, "placement_group_support": true, "intel_avx2": "Yes", "clock_speed_ghz": "Yes", "apiname": "i3.4xlarge", "computeunits": 53.0, "ebs_throughput": 400.0, "vpc_only": true, "max_ips": 240, "physical_processor": "Intel Xeon E5-2686 v4", "fpga": 0, "intel_turbo": "Yes", "enhanced_networking": true, "ebs_iops": 16000.0, "vcpus": 16.0, "memory": 122.0, "ebs_max_bandwidth": 3500.0, "gpus": 0, "ipv6_support": true}, "i2.xlarge": {"ecu_per_vcpu": 3.5, "network_perf": 7.0, "intel_avx": "", "name": "I2 Extra Large", "architecture": "64-bit", "linux_virtualization": "HVM", "storage": 800.0, "placement_group_support": true, "intel_avx2": "", "clock_speed_ghz": "", "apiname": "i2.xlarge", "computeunits": 14.0, "ebs_throughput": 62.5, "vpc_only": false, "max_ips": 60, "physical_processor": "", "fpga": 0, "intel_turbo": "", "enhanced_networking": false, "ebs_iops": 4000.0, "vcpus": 4.0, "memory": 30.5, "ebs_max_bandwidth": 500.0, "gpus": 0, "ipv6_support": true}, "hs1.8xlarge": {"ecu_per_vcpu": 2.1875, "network_perf": 12.0, "intel_avx": "", "name": "High Storage Eight Extra Large", "architecture": "64-bit", "linux_virtualization": "HVM, PV", "storage": 48000.0, "placement_group_support": false, "intel_avx2": "", "clock_speed_ghz": "", "apiname": "hs1.8xlarge", "computeunits": 35.0, "ebs_throughput": 0.0, "vpc_only": false, "max_ips": 240, "physical_processor": "", "fpga": 0, "intel_turbo": "", "enhanced_networking": false, "ebs_iops": 0.0, "vcpus": 16.0, "memory": 117.0, "ebs_max_bandwidth": 0.0, "gpus": 0, "ipv6_support": false}, "t2.micro": {"ecu_per_vcpu": 0.0, "network_perf": 4.0, "intel_avx": "Yes", "name": "T2 Micro", "architecture": "32/64-bit", "linux_virtualization": "HVM", "storage": 0.0, "placement_group_support": false, "intel_avx2": "", "clock_speed_ghz": "Yes", "apiname": "t2.micro", "computeunits": 0.1, "ebs_throughput": 0.0, "vpc_only": true, "max_ips": 4, "physical_processor": "Intel Xeon family", "fpga": 0, "intel_turbo": "Yes", "enhanced_networking": false, "ebs_iops": 0.0, "vcpus": 1.0, "memory": 1.0, "ebs_max_bandwidth": 0.0, "gpus": 0, "ipv6_support": true}, "d2.4xlarge": {"ecu_per_vcpu": 3.5, "network_perf": 9.0, "intel_avx": "Yes", "name": "D2 Quadruple Extra Large", "architecture": "64-bit", "linux_virtualization": "HVM", "storage": 24000.0, "placement_group_support": true, "intel_avx2": "Yes", "clock_speed_ghz": "Yes", "apiname": "d2.4xlarge", "computeunits": 56.0, "ebs_throughput": 250.0, "vpc_only": false, "max_ips": 240, "physical_processor": "Intel Xeon E5-2676 v3", "fpga": 0, "intel_turbo": "Yes", "enhanced_networking": true, "ebs_iops": 16000.0, "vcpus": 16.0, "memory": 122.0, "ebs_max_bandwidth": 2000.0, "gpus": 0, "ipv6_support": true}, "m2.xlarge": {"ecu_per_vcpu": 3.25, "network_perf": 6.0, "intel_avx": "", "name": "M2 High Memory Extra Large", "architecture": "64-bit", "linux_virtualization": "PV", "storage": 420.0, "placement_group_support": false, "intel_avx2": "", "clock_speed_ghz": "", "apiname": "m2.xlarge", "computeunits": 6.5, "ebs_throughput": 0.0, "vpc_only": false, "max_ips": 60, "physical_processor": "", "fpga": 0, "intel_turbo": "", "enhanced_networking": false, "ebs_iops": 0.0, "vcpus": 2.0, "memory": 17.1, "ebs_max_bandwidth": 0.0, "gpus": 0, "ipv6_support": false}, "p2.xlarge": {"ecu_per_vcpu": 3.0, "network_perf": 9.0, "intel_avx": "Yes", "name": "General Purpose GPU Extra Large", "architecture": "64-bit", "linux_virtualization": "HVM", "storage": 0.0, "placement_group_support": true, "intel_avx2": "Yes", "clock_speed_ghz": "Yes", "apiname": "p2.xlarge", "computeunits": 12.0, "ebs_throughput": 93.75, "vpc_only": true, "max_ips": 60, "physical_processor": "Intel Xeon E5-2686 v4", "fpga": 0, "intel_turbo": "Yes", "enhanced_networking": true, "ebs_iops": 6000.0, "vcpus": 4.0, "memory": 61.0, "ebs_max_bandwidth": 750.0, "gpus": 1, "ipv6_support": true}, "i2.4xlarge": {"ecu_per_vcpu": 3.3125, "network_perf": 9.0, "intel_avx": "", "name": "I2 Quadruple Extra Large", "architecture": "64-bit", "linux_virtualization": "HVM", "storage": 3200.0, "placement_group_support": true, "intel_avx2": "", "clock_speed_ghz": "", "apiname": "i2.4xlarge", "computeunits": 53.0, "ebs_throughput": 250.0, "vpc_only": false, "max_ips": 240, "physical_processor": "", "fpga": 0, "intel_turbo": "", "enhanced_networking": false, "ebs_iops": 16000.0, "vcpus": 16.0, "memory": 122.0, "ebs_max_bandwidth": 2000.0, "gpus": 0, "ipv6_support": true}, "t1.micro": {"ecu_per_vcpu": 0.0, "network_perf": 0.0, "intel_avx": "", "name": "T1 Micro", "architecture": "32/64-bit", "linux_virtualization": "PV", "storage": 0.0, "placement_group_support": false, "intel_avx2": "", "clock_speed_ghz": "", "apiname": "t1.micro", "computeunits": 0.0, "ebs_throughput": 0.0, "vpc_only": false, "max_ips": 4, "physical_processor": "", "fpga": 0, "intel_turbo": "", "enhanced_networking": false, "ebs_iops": 0.0, "vcpus": 1.0, "memory": 0.613, "ebs_max_bandwidth": 0.0, "gpus": 0, "ipv6_support": false}, "d2.xlarge": {"ecu_per_vcpu": 3.5, "network_perf": 7.0, "intel_avx": "Yes", "name": "D2 Extra Large", "architecture": "64-bit", "linux_virtualization": "HVM", "storage": 6000.0, "placement_group_support": true, "intel_avx2": "Yes", "clock_speed_ghz": "Yes", "apiname": "d2.xlarge", "computeunits": 14.0, "ebs_throughput": 93.75, "vpc_only": false, "max_ips": 60, "physical_processor": "Intel Xeon E5-2676 v3", "fpga": 0, "intel_turbo": "Yes", "enhanced_networking": true, "ebs_iops": 6000.0, "vcpus": 4.0, "memory": 30.5, "ebs_max_bandwidth": 750.0, "gpus": 0, "ipv6_support": true}, "r3.2xlarge": {"ecu_per_vcpu": 3.25, "network_perf": 9.0, "intel_avx": "Yes", "name": "R3 High-Memory Double Extra Large", "architecture": "64-bit", "linux_virtualization": "HVM", "storage": 160.0, "placement_group_support": true, "intel_avx2": "", "clock_speed_ghz": "Yes", "apiname": "r3.2xlarge", "computeunits": 26.0, "ebs_throughput": 125.0, "vpc_only": false, "max_ips": 60, "physical_processor": "Intel Xeon E5-2670 v2", "fpga": 0, "intel_turbo": "Yes", "enhanced_networking": true, "ebs_iops": 8000.0, "vcpus": 8.0, "memory": 61.0, "ebs_max_bandwidth": 1000.0, "gpus": 0, "ipv6_support": true}, "i3.8xlarge": {"ecu_per_vcpu": 3.09375, "network_perf": 13.0, "intel_avx": "Yes", "name": "I3 High I/O Eight Extra Large", "architecture": "64-bit", "linux_virtualization": "Unknown", "storage": 7600.0, "placement_group_support": true, "intel_avx2": "Yes", "clock_speed_ghz": "Yes", "apiname": "i3.8xlarge", "computeunits": 99.0, "ebs_throughput": 850.0, "vpc_only": true, "max_ips": 240, "physical_processor": "Intel Xeon E5-2686 v4", "fpga": 0, "intel_turbo": "Yes", "enhanced_networking": true, "ebs_iops": 32500.0, "vcpus": 32.0, "memory": 244.0, "ebs_max_bandwidth": 7000.0, "gpus": 0, "ipv6_support": true}, "c3.2xlarge": {"ecu_per_vcpu": 3.5, "network_perf": 9.0, "intel_avx": "Yes", "name": "C3 High-CPU Double Extra Large", "architecture": "64-bit", "linux_virtualization": "HVM, PV", "storage": 160.0, "placement_group_support": true, "intel_avx2": "", "clock_speed_ghz": "Yes", "apiname": "c3.2xlarge", "computeunits": 28.0, "ebs_throughput": 125.0, "vpc_only": false, "max_ips": 60, "physical_processor": "Intel Xeon E5-2680 v2", "fpga": 0, "intel_turbo": "Yes", "enhanced_networking": true, "ebs_iops": 8000.0, "vcpus": 8.0, "memory": 15.0, "ebs_max_bandwidth": 1000.0, "gpus": 0, "ipv6_support": true}, "g2.8xlarge": {"ecu_per_vcpu": 3.25, "network_perf": 16.0, "intel_avx": "", "name": "G2 Eight Extra Large", "architecture": "64-bit", "linux_virtualization": "Unknown", "storage": 240.0, "placement_group_support": true, "intel_avx2": "", "clock_speed_ghz": "", "apiname": "g2.8xlarge", "computeunits": 104.0, "ebs_throughput": 0.0, "vpc_only": false, "max_ips": 240, "physical_processor": "", "fpga": 0, "intel_turbo": "", "enhanced_networking": false, "ebs_iops": 0.0, "vcpus": 32.0, "memory": 60.0, "ebs_max_bandwidth": 0.0, "gpus": 0, "ipv6_support": false}, "t2.medium": {"ecu_per_vcpu": 0.0, "network_perf": 4.0, "intel_avx": "Yes", "name": "T2 Medium", "architecture": "32/64-bit", "linux_virtualization": "HVM", "storage": 0.0, "placement_group_support": false, "intel_avx2": "", "clock_speed_ghz": "Yes", "apiname": "t2.medium", "computeunits": 0.4, "ebs_throughput": 0.0, "vpc_only": true, "max_ips": 18, "physical_processor": "Intel Xeon family", "fpga": 0, "intel_turbo": "Yes", "enhanced_networking": false, "ebs_iops": 0.0, "vcpus": 2.0, "memory": 4.0, "ebs_max_bandwidth": 0.0, "gpus": 0, "ipv6_support": true}, "m4.xlarge": {"ecu_per_vcpu": 3.25, "network_perf": 9.0, "intel_avx": "Yes", "name": "M4 Extra Large", "architecture": "64-bit", "linux_virtualization": "HVM", "storage": 0.0, "placement_group_support": true, "intel_avx2": "Yes", "clock_speed_ghz": "Yes", "apiname": "m4.xlarge", "computeunits": 13.0, "ebs_throughput": 93.75, "vpc_only": true, "max_ips": 60, "physical_processor": "Intel Xeon E5-2676 v3", "fpga": 0, "intel_turbo": "Yes", "enhanced_networking": true, "ebs_iops": 6000.0, "vcpus": 4.0, "memory": 16.0, "ebs_max_bandwidth": 750.0, "gpus": 0, "ipv6_support": true}, "x1.16xlarge": {"ecu_per_vcpu": 2.7265625, "network_perf": 13.0, "intel_avx": "Yes", "name": "X1 Extra High-Memory 16xlarge", "architecture": "64-bit", "linux_virtualization": "HVM", "storage": 1920.0, "placement_group_support": true, "intel_avx2": "Yes", "clock_speed_ghz": "Yes", "apiname": "x1.16xlarge", "computeunits": 174.5, "ebs_throughput": 875.0, "vpc_only": true, "max_ips": 240, "physical_processor": "Intel Xeon E7-8880 v3", "fpga": 0, "intel_turbo": "Yes", "enhanced_networking": true, "ebs_iops": 40000.0, "vcpus": 64.0, "memory": 976.0, "ebs_max_bandwidth": 7000.0, "gpus": 0, "ipv6_support": true}, "p2.8xlarge": {"ecu_per_vcpu": 2.9375, "network_perf": 13.0, "intel_avx": "Yes", "name": "General Purpose GPU Eight Extra Large", "architecture": "64-bit", "linux_virtualization": "HVM", "storage": 0.0, "placement_group_support": true, "intel_avx2": "Yes", "clock_speed_ghz": "Yes", "apiname": "p2.8xlarge", "computeunits": 94.0, "ebs_throughput": 625.0, "vpc_only": true, "max_ips": 240, "physical_processor": "Intel Xeon E5-2686 v4", "fpga": 0, "intel_turbo": "Yes", "enhanced_networking": true, "ebs_iops": 32500.0, "vcpus": 32.0, "memory": 488.0, "ebs_max_bandwidth": 5000.0, "gpus": 8, "ipv6_support": true}, "f1.16xlarge": {"ecu_per_vcpu": 2.9375, "network_perf": 17.0, "intel_avx": "Yes", "name": "F1 16xlarge", "architecture": "64-bit", "linux_virtualization": "HVM", "storage": 3760.0, "placement_group_support": true, "intel_avx2": "Yes", "clock_speed_ghz": "Yes", "apiname": "f1.16xlarge", "computeunits": 188.0, "ebs_throughput": 1750.0, "vpc_only": true, "max_ips": 400, "physical_processor": "Intel Xeon E5-2686 v4", "fpga": 8, "intel_turbo": "Yes", "enhanced_networking": true, "ebs_iops": 75000.0, "vcpus": 64.0, "memory": 976.0, "ebs_max_bandwidth": 14000.0, "gpus": 0, "ipv6_support": true}, "r4.8xlarge": {"ecu_per_vcpu": 3.09375, "network_perf": 13.0, "intel_avx": "Yes", "name": "R4 High-Memory Eight Extra Large", "architecture": "64-bit", "linux_virtualization": "HVM", "storage": 0.0, "placement_group_support": true, "intel_avx2": "Yes", "clock_speed_ghz": "Yes", "apiname": "r4.8xlarge", "computeunits": 99.0, "ebs_throughput": 875.0, "vpc_only": true, "max_ips": 240, "physical_processor": "Intel Xeon E5-2686 v4", "fpga": 0, "intel_turbo": "Yes", "enhanced_networking": true, "ebs_iops": 37500.0, "vcpus": 32.0, "memory": 244.0, "ebs_max_bandwidth": 7000.0, "gpus": 0, "ipv6_support": true}, "g3.4xlarge": {"ecu_per_vcpu": 2.9375, "network_perf": 11.0, "intel_avx": "Yes", "name": "G3 Quadruple Extra Large", "architecture": "64-bit", "linux_virtualization": "HVM", "storage": 0.0, "placement_group_support": true, "intel_avx2": "Yes", "clock_speed_ghz": "Yes", "apiname": "g3.4xlarge", "computeunits": 47.0, "ebs_throughput": 437.0, "vpc_only": true, "max_ips": 240, "physical_processor": "Intel Xeon E5-2686 v4", "fpga": 0, "intel_turbo": "Yes", "enhanced_networking": true, "ebs_iops": 20000.0, "vcpus": 16.0, "memory": 122.0, "ebs_max_bandwidth": 3500.0, "gpus": 1, "ipv6_support": true}, "cg1.4xlarge": {"ecu_per_vcpu": 2.09375, "network_perf": 12.0, "intel_avx": "", "name": "Cluster GPU Quadruple Extra Large", "architecture": "64-bit", "linux_virtualization": "HVM", "storage": 1680.0, "placement_group_support": false, "intel_avx2": "", "clock_speed_ghz": "", "apiname": "cg1.4xlarge", "computeunits": 33.5, "ebs_throughput": 0.0, "vpc_only": false, "max_ips": 240, "physical_processor": "", "fpga": 0, "intel_turbo": "", "enhanced_networking": false, "ebs_iops": 0.0, "vcpus": 16.0, "memory": 22.5, "ebs_max_bandwidth": 0.0, "gpus": 0, "ipv6_support": false}, "c4.large": {"ecu_per_vcpu": 4.0, "network_perf": 7.0, "intel_avx": "Yes", "name": "C4 High-CPU Large", "architecture": "64-bit", "linux_virtualization": "HVM", "storage": 0.0, "placement_group_support": true, "intel_avx2": "Yes", "clock_speed_ghz": "Yes", "apiname": "c4.large", "computeunits": 8.0, "ebs_throughput": 62.5, "vpc_only": true, "max_ips": 30, "physical_processor": "Intel Xeon E5-2666 v3", "fpga": 0, "intel_turbo": "Yes", "enhanced_networking": true, "ebs_iops": 4000.0, "vcpus": 2.0, "memory": 3.75, "ebs_max_bandwidth": 500.0, "gpus": 0, "ipv6_support": true}, "m4.16xlarge": {"ecu_per_vcpu": 2.9375, "network_perf": 17.0, "intel_avx": "Yes", "name": "M4 16xlarge", "architecture": "64-bit", "linux_virtualization": "HVM", "storage": 0.0, "placement_group_support": true, "intel_avx2": "Yes", "clock_speed_ghz": "Yes", "apiname": "m4.16xlarge", "computeunits": 188.0, "ebs_throughput": 1250.0, "vpc_only": true, "max_ips": 240, "physical_processor": "Intel Xeon E5-2686 v4", "fpga": 0, "intel_turbo": "Yes", "enhanced_networking": true, "ebs_iops": 65000.0, "vcpus": 64.0, "memory": 256.0, "ebs_max_bandwidth": 10000.0, "gpus": 0, "ipv6_support": true}, "r4.4xlarge": {"ecu_per_vcpu": 3.3125, "network_perf": 11.0, "intel_avx": "Yes", "name": "R4 High-Memory Quadruple Extra Large", "architecture": "64-bit", "linux_virtualization": "HVM", "storage": 0.0, "placement_group_support": true, "intel_avx2": "Yes", "clock_speed_ghz": "Yes", "apiname": "r4.4xlarge", "computeunits": 53.0, "ebs_throughput": 437.0, "vpc_only": true, "max_ips": 240, "physical_processor": "Intel Xeon E5-2686 v4", "fpga": 0, "intel_turbo": "Yes", "enhanced_networking": true, "ebs_iops": 18750.0, "vcpus": 16.0, "memory": 122.0, "ebs_max_bandwidth": 3500.0, "gpus": 0, "ipv6_support": true}, "r4.2xlarge": {"ecu_per_vcpu": 3.375, "network_perf": 11.0, "intel_avx": "Yes", "name": "R4 High-Memory Double Extra Large", "architecture": "64-bit", "linux_virtualization": "HVM", "storage": 0.0, "placement_group_support": true, "intel_avx2": "Yes", "clock_speed_ghz": "Yes", "apiname": "r4.2xlarge", "computeunits": 27.0, "ebs_throughput": 218.0, "vpc_only": true, "max_ips": 60, "physical_processor": "Intel Xeon E5-2686 v4", "fpga": 0, "intel_turbo": "Yes", "enhanced_networking": true, "ebs_iops": 12000.0, "vcpus": 8.0, "memory": 61.0, "ebs_max_bandwidth": 1750.0, "gpus": 0, "ipv6_support": true}, "c3.xlarge": {"ecu_per_vcpu": 3.5, "network_perf": 7.0, "intel_avx": "Yes", "name": "C3 High-CPU Extra Large", "architecture": "64-bit", "linux_virtualization": "HVM, PV", "storage": 80.0, "placement_group_support": true, "intel_avx2": "", "clock_speed_ghz": "Yes", "apiname": "c3.xlarge", "computeunits": 14.0, "ebs_throughput": 62.5, "vpc_only": false, "max_ips": 60, "physical_processor": "Intel Xeon E5-2680 v2", "fpga": 0, "intel_turbo": "Yes", "enhanced_networking": true, "ebs_iops": 4000.0, "vcpus": 4.0, "memory": 7.5, "ebs_max_bandwidth": 500.0, "gpus": 0, "ipv6_support": true}, "i3.large": {"ecu_per_vcpu": 3.5, "network_perf": 11.0, "intel_avx": "Yes", "name": "I3 High I/O Large", "architecture": "64-bit", "linux_virtualization": "Unknown", "storage": 475.0, "placement_group_support": true, "intel_avx2": "Yes", "clock_speed_ghz": "Yes", "apiname": "i3.large", "computeunits": 7.0, "ebs_throughput": 50.0, "vpc_only": true, "max_ips": 30, "physical_processor": "Intel Xeon E5-2686 v4", "fpga": 0, "intel_turbo": "Yes", "enhanced_networking": true, "ebs_iops": 3000.0, "vcpus": 2.0, "memory": 15.25, "ebs_max_bandwidth": 425.0, "gpus": 0, "ipv6_support": true}, "r4.xlarge": {"ecu_per_vcpu": 3.375, "network_perf": 11.0, "intel_avx": "Yes", "name": "R4 High-Memory Extra Large", "architecture": "64-bit", "linux_virtualization": "HVM", "storage": 0.0, "placement_group_support": true, "intel_avx2": "Yes", "clock_speed_ghz": "Yes", "apiname": "r4.xlarge", "computeunits": 13.5, "ebs_throughput": 109.0, "vpc_only": true, "max_ips": 60, "physical_processor": "Intel Xeon E5-2686 v4", "fpga": 0, "intel_turbo": "Yes", "enhanced_networking": true, "ebs_iops": 6000.0, "vcpus": 4.0, "memory": 30.5, "ebs_max_bandwidth": 875.0, "gpus": 0, "ipv6_support": true}, "m2.2xlarge": {"ecu_per_vcpu": 3.25, "network_perf": 7.0, "intel_avx": "", "name": "M2 High Memory Double Extra Large", "architecture": "64-bit", "linux_virtualization": "PV", "storage": 850.0, "placement_group_support": false, "intel_avx2": "", "clock_speed_ghz": "", "apiname": "m2.2xlarge", "computeunits": 13.0, "ebs_throughput": 62.5, "vpc_only": false, "max_ips": 120, "physical_processor": "", "fpga": 0, "intel_turbo": "", "enhanced_networking": false, "ebs_iops": 4000.0, "vcpus": 4.0, "memory": 34.2, "ebs_max_bandwidth": 500.0, "gpus": 0, "ipv6_support": false}, "m3.medium": {"ecu_per_vcpu": 3.0, "network_perf": 6.0, "intel_avx": "Yes", "name": "M3 General Purpose Medium", "architecture": "64-bit", "linux_virtualization": "HVM, PV", "storage": 4.0, "placement_group_support": false, "intel_avx2": "", "clock_speed_ghz": "Yes", "apiname": "m3.medium", "computeunits": 3.0, "ebs_throughput": 0.0, "vpc_only": false, "max_ips": 12, "physical_processor": "Intel Xeon E5-2670 v2", "fpga": 0, "intel_turbo": "Yes", "enhanced_networking": false, "ebs_iops": 0.0, "vcpus": 1.0, "memory": 3.75, "ebs_max_bandwidth": 0.0, "gpus": 0, "ipv6_support": false}, "r3.4xlarge": {"ecu_per_vcpu": 3.25, "network_perf": 9.0, "intel_avx": "Yes", "name": "R3 High-Memory Quadruple Extra Large", "architecture": "64-bit", "linux_virtualization": "HVM", "storage": 320.0, "placement_group_support": true, "intel_avx2": "", "clock_speed_ghz": "Yes", "apiname": "r3.4xlarge", "computeunits": 52.0, "ebs_throughput": 250.0, "vpc_only": false, "max_ips": 240, "physical_processor": "Intel Xeon E5-2670 v2", "fpga": 0, "intel_turbo": "Yes", "enhanced_networking": true, "ebs_iops": 16000.0, "vcpus": 16.0, "memory": 122.0, "ebs_max_bandwidth": 2000.0, "gpus": 0, "ipv6_support": true}, "t2.small": {"ecu_per_vcpu": 0.0, "network_perf": 4.0, "intel_avx": "Yes", "name": "T2 Small", "architecture": "32/64-bit", "linux_virtualization": "HVM", "storage": 0.0, "placement_group_support": false, "intel_avx2": "", "clock_speed_ghz": "Yes", "apiname": "t2.small", "computeunits": 0.2, "ebs_throughput": 0.0, "vpc_only": true, "max_ips": 8, "physical_processor": "Intel Xeon family", "fpga": 0, "intel_turbo": "Yes", "enhanced_networking": false, "ebs_iops": 0.0, "vcpus": 1.0, "memory": 2.0, "ebs_max_bandwidth": 0.0, "gpus": 0, "ipv6_support": true}, "r3.large": {"ecu_per_vcpu": 3.25, "network_perf": 6.0, "intel_avx": "Yes", "name": "R3 High-Memory Large", "architecture": "64-bit", "linux_virtualization": "HVM", "storage": 32.0, "placement_group_support": true, "intel_avx2": "", "clock_speed_ghz": "Yes", "apiname": "r3.large", "computeunits": 6.5, "ebs_throughput": 0.0, "vpc_only": false, "max_ips": 30, "physical_processor": "Intel Xeon E5-2670 v2", "fpga": 0, "intel_turbo": "Yes", "enhanced_networking": true, "ebs_iops": 0.0, "vcpus": 2.0, "memory": 15.25, "ebs_max_bandwidth": 0.0, "gpus": 0, "ipv6_support": true}, "i3.16xlarge": {"ecu_per_vcpu": 3.125, "network_perf": 17.0, "intel_avx": "Yes", "name": "I3 High I/O 16xlarge", "architecture": "64-bit", "linux_virtualization": "Unknown", "storage": 15200.0, "placement_group_support": true, "intel_avx2": "Yes", "clock_speed_ghz": "Yes", "apiname": "i3.16xlarge", "computeunits": 200.0, "ebs_throughput": 1750.0, "vpc_only": true, "max_ips": 750, "physical_processor": "Intel Xeon E5-2686 v4", "fpga": 0, "intel_turbo": "Yes", "enhanced_networking": true, "ebs_iops": 65000.0, "vcpus": 64.0, "memory": 488.0, "ebs_max_bandwidth": 14000.0, "gpus": 0, "ipv6_support": true}, "c3.large": {"ecu_per_vcpu": 3.5, "network_perf": 6.0, "intel_avx": "Yes", "name": "C3 High-CPU Large", "architecture": "32/64-bit", "linux_virtualization": "HVM, PV", "storage": 32.0, "placement_group_support": true, "intel_avx2": "", "clock_speed_ghz": "Yes", "apiname": "c3.large", "computeunits": 7.0, "ebs_throughput": 0.0, "vpc_only": false, "max_ips": 30, "physical_processor": "Intel Xeon E5-2680 v2", "fpga": 0, "intel_turbo": "Yes", "enhanced_networking": true, "ebs_iops": 0.0, "vcpus": 2.0, "memory": 3.75, "ebs_max_bandwidth": 0.0, "gpus": 0, "ipv6_support": true}, "i2.2xlarge": {"ecu_per_vcpu": 3.375, "network_perf": 7.0, "intel_avx": "", "name": "I2 Double Extra Large", "architecture": "64-bit", "linux_virtualization": "HVM", "storage": 1600.0, "placement_group_support": true, "intel_avx2": "", "clock_speed_ghz": "", "apiname": "i2.2xlarge", "computeunits": 27.0, "ebs_throughput": 125.0, "vpc_only": false, "max_ips": 60, "physical_processor": "", "fpga": 0, "intel_turbo": "", "enhanced_networking": false, "ebs_iops": 8000.0, "vcpus": 8.0, "memory": 61.0, "ebs_max_bandwidth": 1000.0, "gpus": 0, "ipv6_support": true}, "i3.xlarge": {"ecu_per_vcpu": 3.25, "network_perf": 11.0, "intel_avx": "Yes", "name": "I3 High I/O Extra Large", "architecture": "64-bit", "linux_virtualization": "Unknown", "storage": 950.0, "placement_group_support": true, "intel_avx2": "Yes", "clock_speed_ghz": "Yes", "apiname": "i3.xlarge", "computeunits": 13.0, "ebs_throughput": 100.0, "vpc_only": true, "max_ips": 60, "physical_processor": "Intel Xeon E5-2686 v4", "fpga": 0, "intel_turbo": "Yes", "enhanced_networking": true, "ebs_iops": 6000.0, "vcpus": 4.0, "memory": 30.5, "ebs_max_bandwidth": 850.0, "gpus": 0, "ipv6_support": true}, "i2.8xlarge": {"ecu_per_vcpu": 3.25, "network_perf": 13.0, "intel_avx": "", "name": "I2 Eight Extra Large", "architecture": "64-bit", "linux_virtualization": "HVM", "storage": 6400.0, "placement_group_support": true, "intel_avx2": "", "clock_speed_ghz": "", "apiname": "i2.8xlarge", "computeunits": 104.0, "ebs_throughput": 0.0, "vpc_only": false, "max_ips": 240, "physical_processor": "", "fpga": 0, "intel_turbo": "", "enhanced_networking": false, "ebs_iops": 0.0, "vcpus": 32.0, "memory": 244.0, "ebs_max_bandwidth": 0.0, "gpus": 0, "ipv6_support": true}, "r4.16xlarge": {"ecu_per_vcpu": 3.046875, "network_perf": 17.0, "intel_avx": "Yes", "name": "R4 High-Memory 16xlarge", "architecture": "64-bit", "linux_virtualization": "HVM", "storage": 0.0, "placement_group_support": true, "intel_avx2": "Yes", "clock_speed_ghz": "Yes", "apiname": "r4.16xlarge", "computeunits": 195.0, "ebs_throughput": 1750.0, "vpc_only": true, "max_ips": 750, "physical_processor": "Intel Xeon E5-2686 v4", "fpga": 0, "intel_turbo": "Yes", "enhanced_networking": true, "ebs_iops": 75000.0, "vcpus": 64.0, "memory": 488.0, "ebs_max_bandwidth": 14000.0, "gpus": 0, "ipv6_support": true}, "g3.8xlarge": {"ecu_per_vcpu": 2.9375, "network_perf": 13.0, "intel_avx": "Yes", "name": "G3 Eight Extra Large", "architecture": "64-bit", "linux_virtualization": "HVM", "storage": 0.0, "placement_group_support": true, "intel_avx2": "Yes", "clock_speed_ghz": "Yes", "apiname": "g3.8xlarge", "computeunits": 94.0, "ebs_throughput": 875.0, "vpc_only": true, "max_ips": 240, "physical_processor": "Intel Xeon E5-2686 v4", "fpga": 0, "intel_turbo": "Yes", "enhanced_networking": true, "ebs_iops": 40000.0, "vcpus": 32.0, "memory": 244.0, "ebs_max_bandwidth": 7000.0, "gpus": 2, "ipv6_support": true}, "c3.4xlarge": {"ecu_per_vcpu": 3.4375, "network_perf": 9.0, "intel_avx": "Yes", "name": "C3 High-CPU Quadruple Extra Large", "architecture": "64-bit", "linux_virtualization": "HVM, PV", "storage": 320.0, "placement_group_support": true, "intel_avx2": "", "clock_speed_ghz": "Yes", "apiname": "c3.4xlarge", "computeunits": 55.0, "ebs_throughput": 250.0, "vpc_only": false, "max_ips": 240, "physical_processor": "Intel Xeon E5-2680 v2", "fpga": 0, "intel_turbo": "Yes", "enhanced_networking": true, "ebs_iops": 16000.0, "vcpus": 16.0, "memory": 30.0, "ebs_max_bandwidth": 2000.0, "gpus": 0, "ipv6_support": true}, "r4.large": {"ecu_per_vcpu": 3.5, "network_perf": 11.0, "intel_avx": "Yes", "name": "R4 High-Memory Large", "architecture": "64-bit", "linux_virtualization": "HVM", "storage": 0.0, "placement_group_support": true, "intel_avx2": "Yes", "clock_speed_ghz": "Yes", "apiname": "r4.large", "computeunits": 7.0, "ebs_throughput": 54.0, "vpc_only": true, "max_ips": 30, "physical_processor": "Intel Xeon E5-2686 v4", "fpga": 0, "intel_turbo": "Yes", "enhanced_networking": true, "ebs_iops": 3000.0, "vcpus": 2.0, "memory": 15.25, "ebs_max_bandwidth": 437.0, "gpus": 0, "ipv6_support": true}, "f1.2xlarge": {"ecu_per_vcpu": 3.25, "network_perf": 11.0, "intel_avx": "Yes", "name": "F1 Double Extra Large", "architecture": "64-bit", "linux_virtualization": "HVM", "storage": 470.0, "placement_group_support": true, "intel_avx2": "Yes", "clock_speed_ghz": "Yes", "apiname": "f1.2xlarge", "computeunits": 26.0, "ebs_throughput": 200.0, "vpc_only": true, "max_ips": 60, "physical_processor": "Intel Xeon E5-2686 v4", "fpga": 1, "intel_turbo": "Yes", "enhanced_networking": true, "ebs_iops": 12000.0, "vcpus": 8.0, "memory": 122.0, "ebs_max_bandwidth": 1700.0, "gpus": 0, "ipv6_support": true}, "m4.2xlarge": {"ecu_per_vcpu": 3.25, "network_perf": 9.0, "intel_avx": "Yes", "name": "M4 Double Extra Large", "architecture": "64-bit", "linux_virtualization": "HVM", "storage": 0.0, "placement_group_support": true, "intel_avx2": "Yes", "clock_speed_ghz": "Yes", "apiname": "m4.2xlarge", "computeunits": 26.0, "ebs_throughput": 125.0, "vpc_only": true, "max_ips": 60, "physical_processor": "Intel Xeon E5-2676 v3", "fpga": 0, "intel_turbo": "Yes", "enhanced_networking": true, "ebs_iops": 8000.0, "vcpus": 8.0, "memory": 32.0, "ebs_max_bandwidth": 1000.0, "gpus": 0, "ipv6_support": true}, "m3.2xlarge": {"ecu_per_vcpu": 3.25, "network_perf": 9.0, "intel_avx": "Yes", "name": "M3 General Purpose Double Extra Large", "architecture": "64-bit", "linux_virtualization": "HVM, PV", "storage": 160.0, "placement_group_support": false, "intel_avx2": "", "clock_speed_ghz": "Yes", "apiname": "m3.2xlarge", "computeunits": 26.0, "ebs_throughput": 125.0, "vpc_only": false, "max_ips": 120, "physical_processor": "Intel Xeon E5-2670 v2", "fpga": 0, "intel_turbo": "Yes", "enhanced_networking": false, "ebs_iops": 8000.0, "vcpus": 8.0, "memory": 30.0, "ebs_max_bandwidth": 1000.0, "gpus": 0, "ipv6_support": false}, "c3.8xlarge": {"ecu_per_vcpu": 3.375, "network_perf": 12.0, "intel_avx": "Yes", "name": "C3 High-CPU Eight Extra Large", "architecture": "64-bit", "linux_virtualization": "HVM, PV", "storage": 640.0, "placement_group_support": true, "intel_avx2": "", "clock_speed_ghz": "Yes", "apiname": "c3.8xlarge", "computeunits": 108.0, "ebs_throughput": 0.0, "vpc_only": false, "max_ips": 240, "physical_processor": "Intel Xeon E5-2680 v2", "fpga": 0, "intel_turbo": "Yes", "enhanced_networking": true, "ebs_iops": 0.0, "vcpus": 32.0, "memory": 60.0, "ebs_max_bandwidth": 0.0, "gpus": 0, "ipv6_support": true}, "cr1.8xlarge": {"ecu_per_vcpu": 2.75, "network_perf": 12.0, "intel_avx": "", "name": "High Memory Cluster Eight Extra Large", "architecture": "64-bit", "linux_virtualization": "Unknown", "storage": 240.0, "placement_group_support": false, "intel_avx2": "", "clock_speed_ghz": "", "apiname": "cr1.8xlarge", "computeunits": 88.0, "ebs_throughput": 0.0, "vpc_only": false, "max_ips": 240, "physical_processor": "", "fpga": 0, "intel_turbo": "", "enhanced_networking": false, "ebs_iops": 0.0, "vcpus": 32.0, "memory": 244.0, "ebs_max_bandwidth": 0.0, "gpus": 0, "ipv6_support": false}, "cc2.8xlarge": {"ecu_per_vcpu": 2.75, "network_perf": 12.0, "intel_avx": "", "name": "Cluster Compute Eight Extra Large", "architecture": "64-bit", "linux_virtualization": "HVM", "storage": 3360.0, "placement_group_support": false, "intel_avx2": "", "clock_speed_ghz": "", "apiname": "cc2.8xlarge", "computeunits": 88.0, "ebs_throughput": 0.0, "vpc_only": false, "max_ips": 240, "physical_processor": "", "fpga": 0, "intel_turbo": "", "enhanced_networking": false, "ebs_iops": 0.0, "vcpus": 32.0, "memory": 60.5, "ebs_max_bandwidth": 0.0, "gpus": 0, "ipv6_support": false}, "m1.large": {"ecu_per_vcpu": 2.0, "network_perf": 7.0, "intel_avx": "", "name": "M1 General Purpose Large", "architecture": "64-bit", "linux_virtualization": "PV", "storage": 840.0, "placement_group_support": false, "intel_avx2": "", "clock_speed_ghz": "", "apiname": "m1.large", "computeunits": 4.0, "ebs_throughput": 62.5, "vpc_only": false, "max_ips": 30, "physical_processor": "", "fpga": 0, "intel_turbo": "", "enhanced_networking": false, "ebs_iops": 4000.0, "vcpus": 2.0, "memory": 7.5, "ebs_max_bandwidth": 500.0, "gpus": 0, "ipv6_support": false}, "r3.xlarge": {"ecu_per_vcpu": 3.25, "network_perf": 7.0, "intel_avx": "Yes", "name": "R3 High-Memory Extra Large", "architecture": "64-bit", "linux_virtualization": "HVM", "storage": 80.0, "placement_group_support": true, "intel_avx2": "", "clock_speed_ghz": "Yes", "apiname": "r3.xlarge", "computeunits": 13.0, "ebs_throughput": 62.5, "vpc_only": false, "max_ips": 60, "physical_processor": "Intel Xeon E5-2670 v2", "fpga": 0, "intel_turbo": "Yes", "enhanced_networking": true, "ebs_iops": 4000.0, "vcpus": 4.0, "memory": 30.5, "ebs_max_bandwidth": 500.0, "gpus": 0, "ipv6_support": true}, "g3.16xlarge": {"ecu_per_vcpu": 2.9375, "network_perf": 17.0, "intel_avx": "Yes", "name": "G3 16xlarge", "architecture": "64-bit", "linux_virtualization": "HVM", "storage": 0.0, "placement_group_support": true, "intel_avx2": "Yes", "clock_speed_ghz": "Yes", "apiname": "g3.16xlarge", "computeunits": 188.0, "ebs_throughput": 1750.0, "vpc_only": true, "max_ips": 750, "physical_processor": "Intel Xeon E5-2686 v4", "fpga": 0, "intel_turbo": "Yes", "enhanced_networking": true, "ebs_iops": 80000.0, "vcpus": 64.0, "memory": 488.0, "ebs_max_bandwidth": 14000.0, "gpus": 4, "ipv6_support": true}, "m1.medium": {"ecu_per_vcpu": 2.0, "network_perf": 6.0, "intel_avx": "", "name": "M1 General Purpose Medium", "architecture": "32/64-bit", "linux_virtualization": "PV", "storage": 410.0, "placement_group_support": false, "intel_avx2": "", "clock_speed_ghz": "", "apiname": "m1.medium", "computeunits": 2.0, "ebs_throughput": 0.0, "vpc_only": false, "max_ips": 12, "physical_processor": "", "fpga": 0, "intel_turbo": "", "enhanced_networking": false, "ebs_iops": 0.0, "vcpus": 1.0, "memory": 3.75, "ebs_max_bandwidth": 0.0, "gpus": 0, "ipv6_support": false}, "i3.2xlarge": {"ecu_per_vcpu": 3.375, "network_perf": 11.0, "intel_avx": "Yes", "name": "I3 High I/O Double Extra Large", "architecture": "64-bit", "linux_virtualization": "Unknown", "storage": 1900.0, "placement_group_support": true, "intel_avx2": "Yes", "clock_speed_ghz": "Yes", "apiname": "i3.2xlarge", "computeunits": 27.0, "ebs_throughput": 200.0, "vpc_only": true, "max_ips": 60, "physical_processor": "Intel Xeon E5-2686 v4", "fpga": 0, "intel_turbo": "Yes", "enhanced_networking": true, "ebs_iops": 12000.0, "vcpus": 8.0, "memory": 61.0, "ebs_max_bandwidth": 1700.0, "gpus": 0, "ipv6_support": true}, "t2.xlarge": {"ecu_per_vcpu": 0.0, "network_perf": 6.0, "intel_avx": "Yes", "name": "T2 Extra Large", "architecture": "64-bit", "linux_virtualization": "HVM", "storage": 0.0, "placement_group_support": false, "intel_avx2": "", "clock_speed_ghz": "Yes", "apiname": "t2.xlarge", "computeunits": 0.9, "ebs_throughput": 0.0, "vpc_only": true, "max_ips": 45, "physical_processor": "Intel Xeon family", "fpga": 0, "intel_turbo": "Yes", "enhanced_networking": false, "ebs_iops": 0.0, "vcpus": 4.0, "memory": 16.0, "ebs_max_bandwidth": 0.0, "gpus": 0, "ipv6_support": true}, "g2.2xlarge": {"ecu_per_vcpu": 3.25, "network_perf": 16.0, "intel_avx": "", "name": "G2 Double Extra Large", "architecture": "64-bit", "linux_virtualization": "Unknown", "storage": 60.0, "placement_group_support": true, "intel_avx2": "", "clock_speed_ghz": "", "apiname": "g2.2xlarge", "computeunits": 26.0, "ebs_throughput": 125.0, "vpc_only": false, "max_ips": 60, "physical_processor": "", "fpga": 0, "intel_turbo": "", "enhanced_networking": false, "ebs_iops": 8000.0, "vcpus": 8.0, "memory": 15.0, "ebs_max_bandwidth": 1000.0, "gpus": 0, "ipv6_support": false}, "c1.medium": {"ecu_per_vcpu": 2.5, "network_perf": 6.0, "intel_avx": "", "name": "C1 High-CPU Medium", "architecture": "32/64-bit", "linux_virtualization": "PV", "storage": 350.0, "placement_group_support": false, "intel_avx2": "", "clock_speed_ghz": "", "apiname": "c1.medium", "computeunits": 5.0, "ebs_throughput": 0.0, "vpc_only": false, "max_ips": 12, "physical_processor": "", "fpga": 0, "intel_turbo": "", "enhanced_networking": false, "ebs_iops": 0.0, "vcpus": 2.0, "memory": 1.7, "ebs_max_bandwidth": 0.0, "gpus": 0, "ipv6_support": false}, "t2.large": {"ecu_per_vcpu": 0.0, "network_perf": 4.0, "intel_avx": "Yes", "name": "T2 Large", "architecture": "64-bit", "linux_virtualization": "HVM", "storage": 0.0, "placement_group_support": false, "intel_avx2": "", "clock_speed_ghz": "Yes", "apiname": "t2.large", "computeunits": 0.6, "ebs_throughput": 0.0, "vpc_only": true, "max_ips": 36, "physical_processor": "Intel Xeon family", "fpga": 0, "intel_turbo": "Yes", "enhanced_networking": false, "ebs_iops": 0.0, "vcpus": 2.0, "memory": 8.0, "ebs_max_bandwidth": 0.0, "gpus": 0, "ipv6_support": true}, "d2.2xlarge": {"ecu_per_vcpu": 3.5, "network_perf": 9.0, "intel_avx": "Yes", "name": "D2 Double Extra Large", "architecture": "64-bit", "linux_virtualization": "HVM", "storage": 12000.0, "placement_group_support": true, "intel_avx2": "Yes", "clock_speed_ghz": "Yes", "apiname": "d2.2xlarge", "computeunits": 28.0, "ebs_throughput": 125.0, "vpc_only": false, "max_ips": 60, "physical_processor": "Intel Xeon E5-2676 v3", "fpga": 0, "intel_turbo": "Yes", "enhanced_networking": true, "ebs_iops": 8000.0, "vcpus": 8.0, "memory": 61.0, "ebs_max_bandwidth": 1000.0, "gpus": 0, "ipv6_support": true}, "c4.8xlarge": {"ecu_per_vcpu": 3.66666666667, "network_perf": 13.0, "intel_avx": "Yes", "name": "C4 High-CPU Eight Extra Large", "architecture": "64-bit", "linux_virtualization": "HVM", "storage": 0.0, "placement_group_support": true, "intel_avx2": "Yes", "clock_speed_ghz": "Yes", "apiname": "c4.8xlarge", "computeunits": 132.0, "ebs_throughput": 500.0, "vpc_only": true, "max_ips": 240, "physical_processor": "Intel Xeon E5-2666 v3", "fpga": 0, "intel_turbo": "Yes", "enhanced_networking": true, "ebs_iops": 32000.0, "vcpus": 36.0, "memory": 60.0, "ebs_max_bandwidth": 4000.0, "gpus": 0, "ipv6_support": true}, "c4.2xlarge": {"ecu_per_vcpu": 3.875, "network_perf": 9.0, "intel_avx": "Yes", "name": "C4 High-CPU Double Extra Large", "architecture": "64-bit", "linux_virtualization": "HVM", "storage": 0.0, "placement_group_support": true, "intel_avx2": "Yes", "clock_speed_ghz": "Yes", "apiname": "c4.2xlarge", "computeunits": 31.0, "ebs_throughput": 125.0, "vpc_only": true, "max_ips": 60, "physical_processor": "Intel Xeon E5-2666 v3", "fpga": 0, "intel_turbo": "Yes", "enhanced_networking": true, "ebs_iops": 8000.0, "vcpus": 8.0, "memory": 15.0, "ebs_max_bandwidth": 1000.0, "gpus": 0, "ipv6_support": true}, "x1e.32xlarge": {"ecu_per_vcpu": 2.65625, "network_perf": 17.0, "intel_avx": "Yes", "name": "X1E 32xlarge", "architecture": "64-bit", "linux_virtualization": "Unknown", "storage": 3840.0, "placement_group_support": false, "intel_avx2": "Yes", "clock_speed_ghz": "Yes", "apiname": "x1e.32xlarge", "computeunits": 340.0, "ebs_throughput": 1750.0, "vpc_only": true, "max_ips": 240, "physical_processor": "Intel Xeon E7-8880 v3", "fpga": 0, "intel_turbo": "Yes", "enhanced_networking": true, "ebs_iops": 80000.0, "vcpus": 128.0, "memory": 3904.0, "ebs_max_bandwidth": 14000.0, "gpus": 0, "ipv6_support": false}, "m4.10xlarge": {"ecu_per_vcpu": 3.1125, "network_perf": 13.0, "intel_avx": "Yes", "name": "M4 Deca Extra Large", "architecture": "64-bit", "linux_virtualization": "HVM", "storage": 0.0, "placement_group_support": true, "intel_avx2": "Yes", "clock_speed_ghz": "Yes", "apiname": "m4.10xlarge", "computeunits": 124.5, "ebs_throughput": 500.0, "vpc_only": true, "max_ips": 240, "physical_processor": "Intel Xeon E5-2676 v3", "fpga": 0, "intel_turbo": "Yes", "enhanced_networking": true, "ebs_iops": 32000.0, "vcpus": 40.0, "memory": 160.0, "ebs_max_bandwidth": 4000.0, "gpus": 0, "ipv6_support": true}, "t2.2xlarge": {"ecu_per_vcpu": 0.0, "network_perf": 6.0, "intel_avx": "Yes", "name": "T2 Double Extra Large", "architecture": "64-bit", "linux_virtualization": "HVM", "storage": 0.0, "placement_group_support": false, "intel_avx2": "", "clock_speed_ghz": "Yes", "apiname": "t2.2xlarge", "computeunits": 1.35, "ebs_throughput": 0.0, "vpc_only": true, "max_ips": 45, "physical_processor": "Intel Xeon family", "fpga": 0, "intel_turbo": "Yes", "enhanced_networking": false, "ebs_iops": 0.0, "vcpus": 8.0, "memory": 32.0, "ebs_max_bandwidth": 0.0, "gpus": 0, "ipv6_support": true}, "m4.4xlarge": {"ecu_per_vcpu": 3.34375, "network_perf": 9.0, "intel_avx": "Yes", "name": "M4 Quadruple Extra Large", "architecture": "64-bit", "linux_virtualization": "HVM", "storage": 0.0, "placement_group_support": true, "intel_avx2": "Yes", "clock_speed_ghz": "Yes", "apiname": "m4.4xlarge", "computeunits": 53.5, "ebs_throughput": 250.0, "vpc_only": true, "max_ips": 240, "physical_processor": "Intel Xeon E5-2676 v3", "fpga": 0, "intel_turbo": "Yes", "enhanced_networking": true, "ebs_iops": 16000.0, "vcpus": 16.0, "memory": 64.0, "ebs_max_bandwidth": 2000.0, "gpus": 0, "ipv6_support": true}, "t2.nano": {"ecu_per_vcpu": 0.0, "network_perf": 2.0, "intel_avx": "Yes", "name": "T2 Nano", "architecture": "32/64-bit", "linux_virtualization": "HVM", "storage": 0.0, "placement_group_support": false, "intel_avx2": "", "clock_speed_ghz": "Yes", "apiname": "t2.nano", "computeunits": 0.05, "ebs_throughput": 0.0, "vpc_only": true, "max_ips": 4, "physical_processor": "Intel Xeon family", "fpga": 0, "intel_turbo": "Yes", "enhanced_networking": false, "ebs_iops": 0.0, "vcpus": 1.0, "memory": 0.5, "ebs_max_bandwidth": 0.0, "gpus": 0, "ipv6_support": true}, "d2.8xlarge": {"ecu_per_vcpu": 3.22222222222, "network_perf": 13.0, "intel_avx": "Yes", "name": "D2 Eight Extra Large", "architecture": "64-bit", "linux_virtualization": "HVM", "storage": 48000.0, "placement_group_support": true, "intel_avx2": "Yes", "clock_speed_ghz": "Yes", "apiname": "d2.8xlarge", "computeunits": 116.0, "ebs_throughput": 500.0, "vpc_only": false, "max_ips": 240, "physical_processor": "Intel Xeon E5-2676 v3", "fpga": 0, "intel_turbo": "Yes", "enhanced_networking": true, "ebs_iops": 32000.0, "vcpus": 36.0, "memory": 244.0, "ebs_max_bandwidth": 4000.0, "gpus": 0, "ipv6_support": true}, "m3.large": {"ecu_per_vcpu": 3.25, "network_perf": 6.0, "intel_avx": "Yes", "name": "M3 General Purpose Large", "architecture": "64-bit", "linux_virtualization": "HVM, PV", "storage": 32.0, "placement_group_support": false, "intel_avx2": "", "clock_speed_ghz": "Yes", "apiname": "m3.large", "computeunits": 6.5, "ebs_throughput": 0.0, "vpc_only": false, "max_ips": 30, "physical_processor": "Intel Xeon E5-2670 v2", "fpga": 0, "intel_turbo": "Yes", "enhanced_networking": false, "ebs_iops": 0.0, "vcpus": 2.0, "memory": 7.5, "ebs_max_bandwidth": 0.0, "gpus": 0, "ipv6_support": false}, "m2.4xlarge": {"ecu_per_vcpu": 3.25, "network_perf": 9.0, "intel_avx": "", "name": "M2 High Memory Quadruple Extra Large", "architecture": "64-bit", "linux_virtualization": "PV", "storage": 1680.0, "placement_group_support": false, "intel_avx2": "", "clock_speed_ghz": "", "apiname": "m2.4xlarge", "computeunits": 26.0, "ebs_throughput": 125.0, "vpc_only": false, "max_ips": 240, "physical_processor": "", "fpga": 0, "intel_turbo": "", "enhanced_networking": false, "ebs_iops": 8000.0, "vcpus": 8.0, "memory": 68.4, "ebs_max_bandwidth": 1000.0, "gpus": 0, "ipv6_support": false}, "m1.small": {"ecu_per_vcpu": 1.0, "network_perf": 2.0, "intel_avx": "", "name": "M1 General Purpose Small", "architecture": "32/64-bit", "linux_virtualization": "PV", "storage": 160.0, "placement_group_support": false, "intel_avx2": "", "clock_speed_ghz": "", "apiname": "m1.small", "computeunits": 1.0, "ebs_throughput": 0.0, "vpc_only": false, "max_ips": 8, "physical_processor": "", "fpga": 0, "intel_turbo": "", "enhanced_networking": false, "ebs_iops": 0.0, "vcpus": 1.0, "memory": 1.7, "ebs_max_bandwidth": 0.0, "gpus": 0, "ipv6_support": false}, "c1.xlarge": {"ecu_per_vcpu": 2.5, "network_perf": 9.0, "intel_avx": "", "name": "C1 High-CPU Extra Large", "architecture": "64-bit", "linux_virtualization": "PV", "storage": 1680.0, "placement_group_support": false, "intel_avx2": "", "clock_speed_ghz": "", "apiname": "c1.xlarge", "computeunits": 20.0, "ebs_throughput": 125.0, "vpc_only": false, "max_ips": 60, "physical_processor": "", "fpga": 0, "intel_turbo": "", "enhanced_networking": false, "ebs_iops": 8000.0, "vcpus": 8.0, "memory": 7.0, "ebs_max_bandwidth": 1000.0, "gpus": 0, "ipv6_support": false}, "x1.32xlarge": {"ecu_per_vcpu": 2.7265625, "network_perf": 17.0, "intel_avx": "Yes", "name": "X1 Extra High-Memory 32xlarge", "architecture": "64-bit", "linux_virtualization": "HVM", "storage": 3840.0, "placement_group_support": true, "intel_avx2": "Yes", "clock_speed_ghz": "Yes", "apiname": "x1.32xlarge", "computeunits": 349.0, "ebs_throughput": 1750.0, "vpc_only": true, "max_ips": 240, "physical_processor": "Intel Xeon E7-8880 v3", "fpga": 0, "intel_turbo": "Yes", "enhanced_networking": true, "ebs_iops": 80000.0, "vcpus": 128.0, "memory": 1952.0, "ebs_max_bandwidth": 14000.0, "gpus": 0, "ipv6_support": true}, "r3.8xlarge": {"ecu_per_vcpu": 3.25, "network_perf": 12.0, "intel_avx": "Yes", "name": "R3 High-Memory Eight Extra Large", "architecture": "64-bit", "linux_virtualization": "HVM", "storage": 640.0, "placement_group_support": true, "intel_avx2": "", "clock_speed_ghz": "Yes", "apiname": "r3.8xlarge", "computeunits": 104.0, "ebs_throughput": 0.0, "vpc_only": false, "max_ips": 240, "physical_processor": "Intel Xeon E5-2670 v2", "fpga": 0, "intel_turbo": "Yes", "enhanced_networking": true, "ebs_iops": 0.0, "vcpus": 32.0, "memory": 244.0, "ebs_max_bandwidth": 0.0, "gpus": 0, "ipv6_support": true}, "m4.large": {"ecu_per_vcpu": 3.25, "network_perf": 7.0, "intel_avx": "Yes", "name": "M4 Large", "architecture": "64-bit", "linux_virtualization": "HVM", "storage": 0.0, "placement_group_support": true, "intel_avx2": "Yes", "clock_speed_ghz": "Yes", "apiname": "m4.large", "computeunits": 6.5, "ebs_throughput": 56.25, "vpc_only": true, "max_ips": 20, "physical_processor": "Intel Xeon E5-2676 v3", "fpga": 0, "intel_turbo": "Yes", "enhanced_networking": true, "ebs_iops": 3600.0, "vcpus": 2.0, "memory": 8.0, "ebs_max_bandwidth": 450.0, "gpus": 0, "ipv6_support": true}, "p2.16xlarge": {"ecu_per_vcpu": 2.9375, "network_perf": 17.0, "intel_avx": "Yes", "name": "General Purpose GPU 16xlarge", "architecture": "64-bit", "linux_virtualization": "HVM", "storage": 0.0, "placement_group_support": true, "intel_avx2": "Yes", "clock_speed_ghz": "Yes", "apiname": "p2.16xlarge", "computeunits": 188.0, "ebs_throughput": 1250.0, "vpc_only": true, "max_ips": 240, "physical_processor": "Intel Xeon E5-2686 v4", "fpga": 0, "intel_turbo": "Yes", "enhanced_networking": true, "ebs_iops": 65000.0, "vcpus": 64.0, "memory": 732.0, "ebs_max_bandwidth": 10000.0, "gpus": 16, "ipv6_support": true}, "hi1.4xlarge": {"ecu_per_vcpu": 2.1875, "network_perf": 12.0, "intel_avx": "", "name": "HI1. High I/O Quadruple Extra Large", "architecture": "64-bit", "linux_virtualization": "HVM, PV", "storage": 2048.0, "placement_group_support": false, "intel_avx2": "", "clock_speed_ghz": "", "apiname": "hi1.4xlarge", "computeunits": 35.0, "ebs_throughput": 0.0, "vpc_only": false, "max_ips": 240, "physical_processor": "", "fpga": 0, "intel_turbo": "", "enhanced_networking": false, "ebs_iops": 0.0, "vcpus": 16.0, "memory": 60.5, "ebs_max_bandwidth": 0.0, "gpus": 0, "ipv6_support": false}, "c4.4xlarge": {"ecu_per_vcpu": 3.875, "network_perf": 9.0, "intel_avx": "Yes", "name": "C4 High-CPU Quadruple Extra Large", "architecture": "64-bit", "linux_virtualization": "HVM", "storage": 0.0, "placement_group_support": true, "intel_avx2": "Yes", "clock_speed_ghz": "Yes", "apiname": "c4.4xlarge", "computeunits": 62.0, "ebs_throughput": 250.0, "vpc_only": true, "max_ips": 240, "physical_processor": "Intel Xeon E5-2666 v3", "fpga": 0, "intel_turbo": "Yes", "enhanced_networking": true, "ebs_iops": 16000.0, "vcpus": 16.0, "memory": 30.0, "ebs_max_bandwidth": 2000.0, "gpus": 0, "ipv6_support": true}, "c4.xlarge": {"ecu_per_vcpu": 4.0, "network_perf": 9.0, "intel_avx": "Yes", "name": "C4 High-CPU Extra Large", "architecture": "64-bit", "linux_virtualization": "HVM", "storage": 0.0, "placement_group_support": true, "intel_avx2": "Yes", "clock_speed_ghz": "Yes", "apiname": "c4.xlarge", "computeunits": 16.0, "ebs_throughput": 93.75, "vpc_only": true, "max_ips": 60, "physical_processor": "Intel Xeon E5-2666 v3", "fpga": 0, "intel_turbo": "Yes", "enhanced_networking": true, "ebs_iops": 6000.0, "vcpus": 4.0, "memory": 7.5, "ebs_max_bandwidth": 750.0, "gpus": 0, "ipv6_support": true}, "m3.xlarge": {"ecu_per_vcpu": 3.25, "network_perf": 9.0, "intel_avx": "Yes", "name": "M3 General Purpose Extra Large", "architecture": "64-bit", "linux_virtualization": "HVM, PV", "storage": 80.0, "placement_group_support": false, "intel_avx2": "", "clock_speed_ghz": "Yes", "apiname": "m3.xlarge", "computeunits": 13.0, "ebs_throughput": 62.5, "vpc_only": false, "max_ips": 60, "physical_processor": "Intel Xeon E5-2670 v2", "fpga": 0, "intel_turbo": "Yes", "enhanced_networking": false, "ebs_iops": 4000.0, "vcpus": 4.0, "memory": 15.0, "ebs_max_bandwidth": 500.0, "gpus": 0, "ipv6_support": false}} \ No newline at end of file diff --git a/moto/ec2/responses/amis.py b/moto/ec2/responses/amis.py index 74767aa6b..c92471093 100755 --- a/moto/ec2/responses/amis.py +++ b/moto/ec2/responses/amis.py @@ -1,19 +1,14 @@ from __future__ import unicode_literals from moto.core.responses import BaseResponse -from moto.ec2.utils import instance_ids_from_querystring, image_ids_from_querystring, \ - filters_from_querystring, sequence_from_querystring, executable_users_from_querystring +from moto.ec2.utils import filters_from_querystring class AmisResponse(BaseResponse): def create_image(self): name = self.querystring.get('Name')[0] - if "Description" in self.querystring: - description = self.querystring.get('Description')[0] - else: - description = "" - instance_ids = instance_ids_from_querystring(self.querystring) - instance_id = instance_ids[0] + description = self._get_param('Description', if_none='') + instance_id = self._get_param('InstanceId') if self.is_not_dryrun('CreateImage'): image = self.ec2_backend.create_image( instance_id, name, description) @@ -21,12 +16,10 @@ class AmisResponse(BaseResponse): return template.render(image=image) def copy_image(self): - source_image_id = self.querystring.get('SourceImageId')[0] - source_region = self.querystring.get('SourceRegion')[0] - name = self.querystring.get( - 'Name')[0] if self.querystring.get('Name') else None - description = self.querystring.get( - 'Description')[0] if self.querystring.get('Description') else None + source_image_id = self._get_param('SourceImageId') + source_region = self._get_param('SourceRegion') + name = self._get_param('Name') + description = self._get_param('Description') if self.is_not_dryrun('CopyImage'): image = self.ec2_backend.copy_image( source_image_id, source_region, name, description) @@ -34,33 +27,33 @@ class AmisResponse(BaseResponse): return template.render(image=image) def deregister_image(self): - ami_id = self.querystring.get('ImageId')[0] + ami_id = self._get_param('ImageId') if self.is_not_dryrun('DeregisterImage'): success = self.ec2_backend.deregister_image(ami_id) template = self.response_template(DEREGISTER_IMAGE_RESPONSE) return template.render(success=str(success).lower()) def describe_images(self): - ami_ids = image_ids_from_querystring(self.querystring) + ami_ids = self._get_multi_param('ImageId') filters = filters_from_querystring(self.querystring) - exec_users = executable_users_from_querystring(self.querystring) + exec_users = self._get_multi_param('ExecutableBy') images = self.ec2_backend.describe_images( ami_ids=ami_ids, filters=filters, exec_users=exec_users) template = self.response_template(DESCRIBE_IMAGES_RESPONSE) return template.render(images=images) def describe_image_attribute(self): - ami_id = self.querystring.get('ImageId')[0] + ami_id = self._get_param('ImageId') groups = self.ec2_backend.get_launch_permission_groups(ami_id) users = self.ec2_backend.get_launch_permission_users(ami_id) template = self.response_template(DESCRIBE_IMAGE_ATTRIBUTES_RESPONSE) return template.render(ami_id=ami_id, groups=groups, users=users) def modify_image_attribute(self): - ami_id = self.querystring.get('ImageId')[0] - operation_type = self.querystring.get('OperationType')[0] - group = self.querystring.get('UserGroup.1', [None])[0] - user_ids = sequence_from_querystring('UserId', self.querystring) + ami_id = self._get_param('ImageId') + operation_type = self._get_param('OperationType') + group = self._get_param('UserGroup.1') + user_ids = self._get_multi_param('UserId') if self.is_not_dryrun('ModifyImageAttribute'): if (operation_type == 'add'): self.ec2_backend.add_launch_permission( @@ -115,7 +108,7 @@ DESCRIBE_IMAGES_RESPONSE = """ 0: - raise NotImplementedError( - 'Using filters in KeyPairs.describe_key_pairs is not yet implemented') - - keypairs = self.ec2_backend.describe_key_pairs(names) + keypairs = self.ec2_backend.describe_key_pairs(names, filters) template = self.response_template(DESCRIBE_KEY_PAIRS_RESPONSE) return template.render(keypairs=keypairs) def import_key_pair(self): - name = self.querystring.get('KeyName')[0] - material = self.querystring.get('PublicKeyMaterial')[0] + name = self._get_param('KeyName') + material = self._get_param('PublicKeyMaterial') if self.is_not_dryrun('ImportKeyPair'): keypair = self.ec2_backend.import_key_pair(name, material) template = self.response_template(IMPORT_KEYPAIR_RESPONSE) - return template.render(**keypair) + return template.render(keypair=keypair) DESCRIBE_KEY_PAIRS_RESPONSE = """ @@ -54,12 +50,9 @@ DESCRIBE_KEY_PAIRS_RESPONSE = """ - {{ name }} - - {{ fingerprint }} - - {{ material }} - + {{ keypair.name }} + {{ keypair.fingerprint }} + {{ keypair.material }} """ @@ -71,6 +64,6 @@ DELETE_KEY_PAIR_RESPONSE = """ 471f9fdd-8fe2-4a84-86b0-bd3d3e350979 - {{ name }} - {{ fingerprint }} + {{ keypair.name }} + {{ keypair.fingerprint }} """ diff --git a/moto/ec2/responses/network_acls.py b/moto/ec2/responses/network_acls.py index 440069edc..97f370306 100644 --- a/moto/ec2/responses/network_acls.py +++ b/moto/ec2/responses/network_acls.py @@ -1,28 +1,27 @@ from __future__ import unicode_literals from moto.core.responses import BaseResponse -from moto.ec2.utils import filters_from_querystring, \ - network_acl_ids_from_querystring +from moto.ec2.utils import filters_from_querystring class NetworkACLs(BaseResponse): def create_network_acl(self): - vpc_id = self.querystring.get('VpcId')[0] + vpc_id = self._get_param('VpcId') network_acl = self.ec2_backend.create_network_acl(vpc_id) template = self.response_template(CREATE_NETWORK_ACL_RESPONSE) return template.render(network_acl=network_acl) def create_network_acl_entry(self): - network_acl_id = self.querystring.get('NetworkAclId')[0] - rule_number = self.querystring.get('RuleNumber')[0] - protocol = self.querystring.get('Protocol')[0] - rule_action = self.querystring.get('RuleAction')[0] - egress = self.querystring.get('Egress')[0] - cidr_block = self.querystring.get('CidrBlock')[0] - icmp_code = self.querystring.get('Icmp.Code', [None])[0] - icmp_type = self.querystring.get('Icmp.Type', [None])[0] - port_range_from = self.querystring.get('PortRange.From')[0] - port_range_to = self.querystring.get('PortRange.To')[0] + network_acl_id = self._get_param('NetworkAclId') + rule_number = self._get_param('RuleNumber') + protocol = self._get_param('Protocol') + rule_action = self._get_param('RuleAction') + egress = self._get_param('Egress') + cidr_block = self._get_param('CidrBlock') + icmp_code = self._get_param('Icmp.Code') + icmp_type = self._get_param('Icmp.Type') + port_range_from = self._get_param('PortRange.From') + port_range_to = self._get_param('PortRange.To') network_acl_entry = self.ec2_backend.create_network_acl_entry( network_acl_id, rule_number, protocol, rule_action, @@ -33,30 +32,30 @@ class NetworkACLs(BaseResponse): return template.render(network_acl_entry=network_acl_entry) def delete_network_acl(self): - network_acl_id = self.querystring.get('NetworkAclId')[0] + network_acl_id = self._get_param('NetworkAclId') self.ec2_backend.delete_network_acl(network_acl_id) template = self.response_template(DELETE_NETWORK_ACL_ASSOCIATION) return template.render() def delete_network_acl_entry(self): - network_acl_id = self.querystring.get('NetworkAclId')[0] - rule_number = self.querystring.get('RuleNumber')[0] - egress = self.querystring.get('Egress')[0] + network_acl_id = self._get_param('NetworkAclId') + rule_number = self._get_param('RuleNumber') + egress = self._get_param('Egress') self.ec2_backend.delete_network_acl_entry(network_acl_id, rule_number, egress) template = self.response_template(DELETE_NETWORK_ACL_ENTRY_RESPONSE) return template.render() def replace_network_acl_entry(self): - network_acl_id = self.querystring.get('NetworkAclId')[0] - rule_number = self.querystring.get('RuleNumber')[0] - protocol = self.querystring.get('Protocol')[0] - rule_action = self.querystring.get('RuleAction')[0] - egress = self.querystring.get('Egress')[0] - cidr_block = self.querystring.get('CidrBlock')[0] - icmp_code = self.querystring.get('Icmp.Code', [None])[0] - icmp_type = self.querystring.get('Icmp.Type', [None])[0] - port_range_from = self.querystring.get('PortRange.From')[0] - port_range_to = self.querystring.get('PortRange.To')[0] + network_acl_id = self._get_param('NetworkAclId') + rule_number = self._get_param('RuleNumber') + protocol = self._get_param('Protocol') + rule_action = self._get_param('RuleAction') + egress = self._get_param('Egress') + cidr_block = self._get_param('CidrBlock') + icmp_code = self._get_param('Icmp.Code') + icmp_type = self._get_param('Icmp.Type') + port_range_from = self._get_param('PortRange.From') + port_range_to = self._get_param('PortRange.To') self.ec2_backend.replace_network_acl_entry( network_acl_id, rule_number, protocol, rule_action, @@ -67,7 +66,7 @@ class NetworkACLs(BaseResponse): return template.render() def describe_network_acls(self): - network_acl_ids = network_acl_ids_from_querystring(self.querystring) + network_acl_ids = self._get_multi_param('NetworkAclId') filters = filters_from_querystring(self.querystring) network_acls = self.ec2_backend.get_all_network_acls( network_acl_ids, filters) @@ -75,8 +74,8 @@ class NetworkACLs(BaseResponse): return template.render(network_acls=network_acls) def replace_network_acl_association(self): - association_id = self.querystring.get('AssociationId')[0] - network_acl_id = self.querystring.get('NetworkAclId')[0] + association_id = self._get_param('AssociationId') + network_acl_id = self._get_param('NetworkAclId') association = self.ec2_backend.replace_network_acl_association( association_id, diff --git a/moto/ec2/responses/route_tables.py b/moto/ec2/responses/route_tables.py index 6f68a6553..3878f325d 100644 --- a/moto/ec2/responses/route_tables.py +++ b/moto/ec2/responses/route_tables.py @@ -1,29 +1,25 @@ from __future__ import unicode_literals from moto.core.responses import BaseResponse -from moto.ec2.utils import route_table_ids_from_querystring, filters_from_querystring, optional_from_querystring +from moto.ec2.utils import filters_from_querystring class RouteTables(BaseResponse): def associate_route_table(self): - route_table_id = self.querystring.get('RouteTableId')[0] - subnet_id = self.querystring.get('SubnetId')[0] + route_table_id = self._get_param('RouteTableId') + subnet_id = self._get_param('SubnetId') association_id = self.ec2_backend.associate_route_table( route_table_id, subnet_id) template = self.response_template(ASSOCIATE_ROUTE_TABLE_RESPONSE) return template.render(association_id=association_id) def create_route(self): - route_table_id = self.querystring.get('RouteTableId')[0] - destination_cidr_block = self.querystring.get( - 'DestinationCidrBlock')[0] - - gateway_id = optional_from_querystring('GatewayId', self.querystring) - instance_id = optional_from_querystring('InstanceId', self.querystring) - interface_id = optional_from_querystring( - 'NetworkInterfaceId', self.querystring) - pcx_id = optional_from_querystring( - 'VpcPeeringConnectionId', self.querystring) + route_table_id = self._get_param('RouteTableId') + destination_cidr_block = self._get_param('DestinationCidrBlock') + gateway_id = self._get_param('GatewayId') + instance_id = self._get_param('InstanceId') + interface_id = self._get_param('NetworkInterfaceId') + pcx_id = self._get_param('VpcPeeringConnectionId') self.ec2_backend.create_route(route_table_id, destination_cidr_block, gateway_id=gateway_id, @@ -35,27 +31,26 @@ class RouteTables(BaseResponse): return template.render() def create_route_table(self): - vpc_id = self.querystring.get('VpcId')[0] + vpc_id = self._get_param('VpcId') route_table = self.ec2_backend.create_route_table(vpc_id) template = self.response_template(CREATE_ROUTE_TABLE_RESPONSE) return template.render(route_table=route_table) def delete_route(self): - route_table_id = self.querystring.get('RouteTableId')[0] - destination_cidr_block = self.querystring.get( - 'DestinationCidrBlock')[0] + route_table_id = self._get_param('RouteTableId') + destination_cidr_block = self._get_param('DestinationCidrBlock') self.ec2_backend.delete_route(route_table_id, destination_cidr_block) template = self.response_template(DELETE_ROUTE_RESPONSE) return template.render() def delete_route_table(self): - route_table_id = self.querystring.get('RouteTableId')[0] + route_table_id = self._get_param('RouteTableId') self.ec2_backend.delete_route_table(route_table_id) template = self.response_template(DELETE_ROUTE_TABLE_RESPONSE) return template.render() def describe_route_tables(self): - route_table_ids = route_table_ids_from_querystring(self.querystring) + route_table_ids = self._get_multi_param('RouteTableId') filters = filters_from_querystring(self.querystring) route_tables = self.ec2_backend.get_all_route_tables( route_table_ids, filters) @@ -63,22 +58,18 @@ class RouteTables(BaseResponse): return template.render(route_tables=route_tables) def disassociate_route_table(self): - association_id = self.querystring.get('AssociationId')[0] + association_id = self._get_param('AssociationId') self.ec2_backend.disassociate_route_table(association_id) template = self.response_template(DISASSOCIATE_ROUTE_TABLE_RESPONSE) return template.render() def replace_route(self): - route_table_id = self.querystring.get('RouteTableId')[0] - destination_cidr_block = self.querystring.get( - 'DestinationCidrBlock')[0] - - gateway_id = optional_from_querystring('GatewayId', self.querystring) - instance_id = optional_from_querystring('InstanceId', self.querystring) - interface_id = optional_from_querystring( - 'NetworkInterfaceId', self.querystring) - pcx_id = optional_from_querystring( - 'VpcPeeringConnectionId', self.querystring) + route_table_id = self._get_param('RouteTableId') + destination_cidr_block = self._get_param('DestinationCidrBlock') + gateway_id = self._get_param('GatewayId') + instance_id = self._get_param('InstanceId') + interface_id = self._get_param('NetworkInterfaceId') + pcx_id = self._get_param('VpcPeeringConnectionId') self.ec2_backend.replace_route(route_table_id, destination_cidr_block, gateway_id=gateway_id, @@ -90,8 +81,8 @@ class RouteTables(BaseResponse): return template.render() def replace_route_table_association(self): - route_table_id = self.querystring.get('RouteTableId')[0] - association_id = self.querystring.get('AssociationId')[0] + route_table_id = self._get_param('RouteTableId') + association_id = self._get_param('AssociationId') new_association_id = self.ec2_backend.replace_route_table_association( association_id, route_table_id) template = self.response_template( diff --git a/moto/ec2/responses/security_groups.py b/moto/ec2/responses/security_groups.py index b8cd87de8..9118c01b3 100644 --- a/moto/ec2/responses/security_groups.py +++ b/moto/ec2/responses/security_groups.py @@ -11,69 +11,66 @@ def try_parse_int(value, default=None): return default -def process_rules_from_querystring(querystring): - try: - group_name_or_id = querystring.get('GroupName')[0] - except: - group_name_or_id = querystring.get('GroupId')[0] - - querytree = {} - for key, value in querystring.items(): - key_splitted = key.split('.') - key_splitted = [try_parse_int(e, e) for e in key_splitted] - - d = querytree - for subkey in key_splitted[:-1]: - if subkey not in d: - d[subkey] = {} - d = d[subkey] - d[key_splitted[-1]] = value - - ip_permissions = querytree.get('IpPermissions') or {} - for ip_permission_idx in sorted(ip_permissions.keys()): - ip_permission = ip_permissions[ip_permission_idx] - - ip_protocol = ip_permission.get('IpProtocol', [None])[0] - from_port = ip_permission.get('FromPort', [None])[0] - to_port = ip_permission.get('ToPort', [None])[0] - - ip_ranges = [] - ip_ranges_tree = ip_permission.get('IpRanges') or {} - for ip_range_idx in sorted(ip_ranges_tree.keys()): - ip_ranges.append(ip_ranges_tree[ip_range_idx]['CidrIp'][0]) - - source_groups = [] - source_group_ids = [] - groups_tree = ip_permission.get('Groups') or {} - for group_idx in sorted(groups_tree.keys()): - group_dict = groups_tree[group_idx] - if 'GroupId' in group_dict: - source_group_ids.append(group_dict['GroupId'][0]) - elif 'GroupName' in group_dict: - source_groups.append(group_dict['GroupName'][0]) - - yield (group_name_or_id, ip_protocol, from_port, to_port, ip_ranges, - source_groups, source_group_ids) - - class SecurityGroups(BaseResponse): + def _process_rules_from_querystring(self): + group_name_or_id = (self._get_param('GroupName') or + self._get_param('GroupId')) + + querytree = {} + for key, value in self.querystring.items(): + key_splitted = key.split('.') + key_splitted = [try_parse_int(e, e) for e in key_splitted] + + d = querytree + for subkey in key_splitted[:-1]: + if subkey not in d: + d[subkey] = {} + d = d[subkey] + d[key_splitted[-1]] = value + + ip_permissions = querytree.get('IpPermissions') or {} + for ip_permission_idx in sorted(ip_permissions.keys()): + ip_permission = ip_permissions[ip_permission_idx] + + ip_protocol = ip_permission.get('IpProtocol', [None])[0] + from_port = ip_permission.get('FromPort', [None])[0] + to_port = ip_permission.get('ToPort', [None])[0] + + ip_ranges = [] + ip_ranges_tree = ip_permission.get('IpRanges') or {} + for ip_range_idx in sorted(ip_ranges_tree.keys()): + ip_ranges.append(ip_ranges_tree[ip_range_idx]['CidrIp'][0]) + + source_groups = [] + source_group_ids = [] + groups_tree = ip_permission.get('Groups') or {} + for group_idx in sorted(groups_tree.keys()): + group_dict = groups_tree[group_idx] + if 'GroupId' in group_dict: + source_group_ids.append(group_dict['GroupId'][0]) + elif 'GroupName' in group_dict: + source_groups.append(group_dict['GroupName'][0]) + + yield (group_name_or_id, ip_protocol, from_port, to_port, ip_ranges, + source_groups, source_group_ids) + def authorize_security_group_egress(self): if self.is_not_dryrun('GrantSecurityGroupEgress'): - for args in process_rules_from_querystring(self.querystring): + for args in self._process_rules_from_querystring(): self.ec2_backend.authorize_security_group_egress(*args) return AUTHORIZE_SECURITY_GROUP_EGRESS_RESPONSE def authorize_security_group_ingress(self): if self.is_not_dryrun('GrantSecurityGroupIngress'): - for args in process_rules_from_querystring(self.querystring): + for args in self._process_rules_from_querystring(): self.ec2_backend.authorize_security_group_ingress(*args) return AUTHORIZE_SECURITY_GROUP_INGRESS_REPONSE def create_security_group(self): - name = self.querystring.get('GroupName')[0] - description = self.querystring.get('GroupDescription', [None])[0] - vpc_id = self.querystring.get("VpcId", [None])[0] + name = self._get_param('GroupName') + description = self._get_param('GroupDescription') + vpc_id = self._get_param('VpcId') if self.is_not_dryrun('CreateSecurityGroup'): group = self.ec2_backend.create_security_group( @@ -86,14 +83,14 @@ class SecurityGroups(BaseResponse): # See # http://docs.aws.amazon.com/AWSEC2/latest/APIReference/ApiReference-query-DeleteSecurityGroup.html - name = self.querystring.get('GroupName') - sg_id = self.querystring.get('GroupId') + name = self._get_param('GroupName') + sg_id = self._get_param('GroupId') if self.is_not_dryrun('DeleteSecurityGroup'): if name: - self.ec2_backend.delete_security_group(name[0]) + self.ec2_backend.delete_security_group(name) elif sg_id: - self.ec2_backend.delete_security_group(group_id=sg_id[0]) + self.ec2_backend.delete_security_group(group_id=sg_id) return DELETE_GROUP_RESPONSE @@ -113,7 +110,7 @@ class SecurityGroups(BaseResponse): def revoke_security_group_egress(self): if self.is_not_dryrun('RevokeSecurityGroupEgress'): - for args in process_rules_from_querystring(self.querystring): + for args in self._process_rules_from_querystring(): success = self.ec2_backend.revoke_security_group_egress(*args) if not success: return "Could not find a matching egress rule", dict(status=404) @@ -121,7 +118,7 @@ class SecurityGroups(BaseResponse): def revoke_security_group_ingress(self): if self.is_not_dryrun('RevokeSecurityGroupIngress'): - for args in process_rules_from_querystring(self.querystring): + for args in self._process_rules_from_querystring(): self.ec2_backend.revoke_security_group_ingress(*args) return REVOKE_SECURITY_GROUP_INGRESS_REPONSE diff --git a/moto/ec2/responses/subnets.py b/moto/ec2/responses/subnets.py index 67fd09a14..ba4f78a5e 100644 --- a/moto/ec2/responses/subnets.py +++ b/moto/ec2/responses/subnets.py @@ -7,14 +7,11 @@ from moto.ec2.utils import filters_from_querystring class Subnets(BaseResponse): def create_subnet(self): - vpc_id = self.querystring.get('VpcId')[0] - cidr_block = self.querystring.get('CidrBlock')[0] - if 'AvailabilityZone' in self.querystring: - availability_zone = self.querystring['AvailabilityZone'][0] - else: - zone = random.choice( - self.ec2_backend.describe_availability_zones()) - availability_zone = zone.name + vpc_id = self._get_param('VpcId') + cidr_block = self._get_param('CidrBlock') + availability_zone = self._get_param( + 'AvailabilityZone', if_none=random.choice( + self.ec2_backend.describe_availability_zones()).name) subnet = self.ec2_backend.create_subnet( vpc_id, cidr_block, @@ -24,30 +21,21 @@ class Subnets(BaseResponse): return template.render(subnet=subnet) def delete_subnet(self): - subnet_id = self.querystring.get('SubnetId')[0] + subnet_id = self._get_param('SubnetId') subnet = self.ec2_backend.delete_subnet(subnet_id) template = self.response_template(DELETE_SUBNET_RESPONSE) return template.render(subnet=subnet) def describe_subnets(self): + subnet_ids = self._get_multi_param('SubnetId') filters = filters_from_querystring(self.querystring) - - subnet_ids = [] - idx = 1 - key = 'SubnetId.{0}'.format(idx) - while key in self.querystring: - v = self.querystring[key] - subnet_ids.append(v[0]) - idx += 1 - key = 'SubnetId.{0}'.format(idx) - subnets = self.ec2_backend.get_all_subnets(subnet_ids, filters) template = self.response_template(DESCRIBE_SUBNETS_RESPONSE) return template.render(subnets=subnets) def modify_subnet_attribute(self): - subnet_id = self.querystring.get('SubnetId')[0] - map_public_ip = self.querystring.get('MapPublicIpOnLaunch.Value')[0] + subnet_id = self._get_param('SubnetId') + map_public_ip = self._get_param('MapPublicIpOnLaunch.Value') self.ec2_backend.modify_subnet_attribute(subnet_id, map_public_ip) return MODIFY_SUBNET_ATTRIBUTE_RESPONSE diff --git a/moto/ec2/responses/tags.py b/moto/ec2/responses/tags.py index a747067fb..65d3da255 100644 --- a/moto/ec2/responses/tags.py +++ b/moto/ec2/responses/tags.py @@ -2,14 +2,13 @@ from __future__ import unicode_literals from moto.core.responses import BaseResponse from moto.ec2.models import validate_resource_ids -from moto.ec2.utils import sequence_from_querystring, tags_from_query_string, filters_from_querystring +from moto.ec2.utils import tags_from_query_string, filters_from_querystring class TagResponse(BaseResponse): def create_tags(self): - resource_ids = sequence_from_querystring( - 'ResourceId', self.querystring) + resource_ids = self._get_multi_param('ResourceId') validate_resource_ids(resource_ids) self.ec2_backend.do_resources_exist(resource_ids) tags = tags_from_query_string(self.querystring) @@ -18,8 +17,7 @@ class TagResponse(BaseResponse): return CREATE_RESPONSE def delete_tags(self): - resource_ids = sequence_from_querystring( - 'ResourceId', self.querystring) + resource_ids = self._get_multi_param('ResourceId') validate_resource_ids(resource_ids) tags = tags_from_query_string(self.querystring) if self.is_not_dryrun('DeleteTags'): diff --git a/moto/ec2/responses/virtual_private_gateways.py b/moto/ec2/responses/virtual_private_gateways.py index 2a677d36c..75de31b93 100644 --- a/moto/ec2/responses/virtual_private_gateways.py +++ b/moto/ec2/responses/virtual_private_gateways.py @@ -6,8 +6,8 @@ from moto.ec2.utils import filters_from_querystring class VirtualPrivateGateways(BaseResponse): def attach_vpn_gateway(self): - vpn_gateway_id = self.querystring.get('VpnGatewayId')[0] - vpc_id = self.querystring.get('VpcId')[0] + vpn_gateway_id = self._get_param('VpnGatewayId') + vpc_id = self._get_param('VpcId') attachment = self.ec2_backend.attach_vpn_gateway( vpn_gateway_id, vpc_id @@ -16,13 +16,13 @@ class VirtualPrivateGateways(BaseResponse): return template.render(attachment=attachment) def create_vpn_gateway(self): - type = self.querystring.get('Type', None)[0] + type = self._get_param('Type') vpn_gateway = self.ec2_backend.create_vpn_gateway(type) template = self.response_template(CREATE_VPN_GATEWAY_RESPONSE) return template.render(vpn_gateway=vpn_gateway) def delete_vpn_gateway(self): - vpn_gateway_id = self.querystring.get('VpnGatewayId')[0] + vpn_gateway_id = self._get_param('VpnGatewayId') vpn_gateway = self.ec2_backend.delete_vpn_gateway(vpn_gateway_id) template = self.response_template(DELETE_VPN_GATEWAY_RESPONSE) return template.render(vpn_gateway=vpn_gateway) @@ -34,8 +34,8 @@ class VirtualPrivateGateways(BaseResponse): return template.render(vpn_gateways=vpn_gateways) def detach_vpn_gateway(self): - vpn_gateway_id = self.querystring.get('VpnGatewayId')[0] - vpc_id = self.querystring.get('VpcId')[0] + vpn_gateway_id = self._get_param('VpnGatewayId') + vpc_id = self._get_param('VpcId') attachment = self.ec2_backend.detach_vpn_gateway( vpn_gateway_id, vpc_id diff --git a/moto/ec2/responses/vpc_peering_connections.py b/moto/ec2/responses/vpc_peering_connections.py index deedcc0e6..1bccce4f6 100644 --- a/moto/ec2/responses/vpc_peering_connections.py +++ b/moto/ec2/responses/vpc_peering_connections.py @@ -5,16 +5,15 @@ from moto.core.responses import BaseResponse class VPCPeeringConnections(BaseResponse): def create_vpc_peering_connection(self): - vpc = self.ec2_backend.get_vpc(self.querystring.get('VpcId')[0]) - peer_vpc = self.ec2_backend.get_vpc( - self.querystring.get('PeerVpcId')[0]) + vpc = self.ec2_backend.get_vpc(self._get_param('VpcId')) + peer_vpc = self.ec2_backend.get_vpc(self._get_param('PeerVpcId')) vpc_pcx = self.ec2_backend.create_vpc_peering_connection(vpc, peer_vpc) template = self.response_template( CREATE_VPC_PEERING_CONNECTION_RESPONSE) return template.render(vpc_pcx=vpc_pcx) def delete_vpc_peering_connection(self): - vpc_pcx_id = self.querystring.get('VpcPeeringConnectionId')[0] + vpc_pcx_id = self._get_param('VpcPeeringConnectionId') vpc_pcx = self.ec2_backend.delete_vpc_peering_connection(vpc_pcx_id) template = self.response_template( DELETE_VPC_PEERING_CONNECTION_RESPONSE) @@ -27,14 +26,14 @@ class VPCPeeringConnections(BaseResponse): return template.render(vpc_pcxs=vpc_pcxs) def accept_vpc_peering_connection(self): - vpc_pcx_id = self.querystring.get('VpcPeeringConnectionId')[0] + vpc_pcx_id = self._get_param('VpcPeeringConnectionId') vpc_pcx = self.ec2_backend.accept_vpc_peering_connection(vpc_pcx_id) template = self.response_template( ACCEPT_VPC_PEERING_CONNECTION_RESPONSE) return template.render(vpc_pcx=vpc_pcx) def reject_vpc_peering_connection(self): - vpc_pcx_id = self.querystring.get('VpcPeeringConnectionId')[0] + vpc_pcx_id = self._get_param('VpcPeeringConnectionId') self.ec2_backend.reject_vpc_peering_connection(vpc_pcx_id) template = self.response_template( REJECT_VPC_PEERING_CONNECTION_RESPONSE) diff --git a/moto/ec2/responses/vpcs.py b/moto/ec2/responses/vpcs.py index 129f91a3b..8a53151e0 100644 --- a/moto/ec2/responses/vpcs.py +++ b/moto/ec2/responses/vpcs.py @@ -1,42 +1,41 @@ from __future__ import unicode_literals from moto.core.responses import BaseResponse from moto.core.utils import camelcase_to_underscores -from moto.ec2.utils import filters_from_querystring, vpc_ids_from_querystring +from moto.ec2.utils import filters_from_querystring class VPCs(BaseResponse): def create_vpc(self): - cidr_block = self.querystring.get('CidrBlock')[0] - instance_tenancy = self.querystring.get( - 'InstanceTenancy', ['default'])[0] + cidr_block = self._get_param('CidrBlock') + instance_tenancy = self._get_param('InstanceTenancy', if_none='default') vpc = self.ec2_backend.create_vpc(cidr_block, instance_tenancy) template = self.response_template(CREATE_VPC_RESPONSE) return template.render(vpc=vpc) def delete_vpc(self): - vpc_id = self.querystring.get('VpcId')[0] + vpc_id = self._get_param('VpcId') vpc = self.ec2_backend.delete_vpc(vpc_id) template = self.response_template(DELETE_VPC_RESPONSE) return template.render(vpc=vpc) def describe_vpcs(self): - vpc_ids = vpc_ids_from_querystring(self.querystring) + vpc_ids = self._get_multi_param('VpcId') filters = filters_from_querystring(self.querystring) vpcs = self.ec2_backend.get_all_vpcs(vpc_ids=vpc_ids, filters=filters) template = self.response_template(DESCRIBE_VPCS_RESPONSE) return template.render(vpcs=vpcs) def describe_vpc_attribute(self): - vpc_id = self.querystring.get('VpcId')[0] - attribute = self.querystring.get('Attribute')[0] + vpc_id = self._get_param('VpcId') + attribute = self._get_param('Attribute') attr_name = camelcase_to_underscores(attribute) value = self.ec2_backend.describe_vpc_attribute(vpc_id, attr_name) template = self.response_template(DESCRIBE_VPC_ATTRIBUTE_RESPONSE) return template.render(vpc_id=vpc_id, attribute=attribute, value=value) def modify_vpc_attribute(self): - vpc_id = self.querystring.get('VpcId')[0] + vpc_id = self._get_param('VpcId') for attribute in ('EnableDnsSupport', 'EnableDnsHostnames'): if self.querystring.get('%s.Value' % attribute): diff --git a/moto/ec2/responses/vpn_connections.py b/moto/ec2/responses/vpn_connections.py index 2a4a7ef99..276e3ca99 100644 --- a/moto/ec2/responses/vpn_connections.py +++ b/moto/ec2/responses/vpn_connections.py @@ -1,30 +1,29 @@ from __future__ import unicode_literals from moto.core.responses import BaseResponse -from moto.ec2.utils import filters_from_querystring, sequence_from_querystring +from moto.ec2.utils import filters_from_querystring class VPNConnections(BaseResponse): def create_vpn_connection(self): - type = self.querystring.get("Type", [None])[0] - cgw_id = self.querystring.get("CustomerGatewayId", [None])[0] - vgw_id = self.querystring.get("VPNGatewayId", [None])[0] - static_routes = self.querystring.get("StaticRoutesOnly", [None])[0] + type = self._get_param('Type') + cgw_id = self._get_param('CustomerGatewayId') + vgw_id = self._get_param('VPNGatewayId') + static_routes = self._get_param('StaticRoutesOnly') vpn_connection = self.ec2_backend.create_vpn_connection( type, cgw_id, vgw_id, static_routes_only=static_routes) template = self.response_template(CREATE_VPN_CONNECTION_RESPONSE) return template.render(vpn_connection=vpn_connection) def delete_vpn_connection(self): - vpn_connection_id = self.querystring.get('VpnConnectionId')[0] + vpn_connection_id = self._get_param('VpnConnectionId') vpn_connection = self.ec2_backend.delete_vpn_connection( vpn_connection_id) template = self.response_template(DELETE_VPN_CONNECTION_RESPONSE) return template.render(vpn_connection=vpn_connection) def describe_vpn_connections(self): - vpn_connection_ids = sequence_from_querystring( - 'VpnConnectionId', self.querystring) + vpn_connection_ids = self._get_multi_param('VpnConnectionId') filters = filters_from_querystring(self.querystring) vpn_connections = self.ec2_backend.get_all_vpn_connections( vpn_connection_ids=vpn_connection_ids, filters=filters) diff --git a/moto/ec2/utils.py b/moto/ec2/utils.py index 0e1a19bd1..ab54ea3a8 100644 --- a/moto/ec2/utils.py +++ b/moto/ec2/utils.py @@ -174,62 +174,6 @@ def split_route_id(route_id): return values[0], values[1] -def instance_ids_from_querystring(querystring_dict): - instance_ids = [] - for key, value in querystring_dict.items(): - if 'InstanceId' in key: - instance_ids.append(value[0]) - return instance_ids - - -def image_ids_from_querystring(querystring_dict): - image_ids = [] - for key, value in querystring_dict.items(): - if 'ImageId' in key: - image_ids.append(value[0]) - return image_ids - - -def executable_users_from_querystring(querystring_dict): - user_ids = [] - for key, value in querystring_dict.items(): - if 'ExecutableBy' in key: - user_ids.append(value[0]) - return user_ids - - -def route_table_ids_from_querystring(querystring_dict): - route_table_ids = [] - for key, value in querystring_dict.items(): - if 'RouteTableId' in key: - route_table_ids.append(value[0]) - return route_table_ids - - -def network_acl_ids_from_querystring(querystring_dict): - network_acl_ids = [] - for key, value in querystring_dict.items(): - if 'NetworkAclId' in key: - network_acl_ids.append(value[0]) - return network_acl_ids - - -def vpc_ids_from_querystring(querystring_dict): - vpc_ids = [] - for key, value in querystring_dict.items(): - if 'VpcId' in key: - vpc_ids.append(value[0]) - return vpc_ids - - -def sequence_from_querystring(parameter, querystring_dict): - parameter_values = [] - for key, value in querystring_dict.items(): - if parameter in key: - parameter_values.append(value[0]) - return parameter_values - - def tags_from_query_string(querystring_dict): prefix = 'Tag' suffix = 'Key' @@ -286,11 +230,6 @@ def dhcp_configuration_from_querystring(querystring, option=u'DhcpConfiguration' return response_values -def optional_from_querystring(parameter, querystring): - parameter_array = querystring.get(parameter) - return parameter_array[0] if parameter_array else None - - def filters_from_querystring(querystring_dict): response_values = {} for key, value in querystring_dict.items(): @@ -319,14 +258,6 @@ def dict_from_querystring(parameter, querystring_dict): return use_dict -def keypair_names_from_querystring(querystring_dict): - keypair_names = [] - for key, value in querystring_dict.items(): - if 'KeyName' in key: - keypair_names.append(value[0]) - return keypair_names - - def get_object_value(obj, attr): keys = attr.split('.') val = obj @@ -335,6 +266,11 @@ def get_object_value(obj, attr): val = getattr(val, key) elif isinstance(val, dict): val = val[key] + elif isinstance(val, list): + for item in val: + item_val = get_object_value(item, key) + if item_val: + return item_val else: return None return val @@ -385,14 +321,17 @@ filter_dict_attribute_mapping = { 'state-reason-code': '_state_reason.code', 'source-dest-check': 'source_dest_check', 'vpc-id': 'vpc_id', - 'group-id': 'security_groups', - 'instance.group-id': 'security_groups', + 'group-id': 'security_groups.id', + 'instance.group-id': 'security_groups.id', + 'instance.group-name': 'security_groups.name', 'instance-type': 'instance_type', 'private-ip-address': 'private_ip', 'ip-address': 'public_ip', 'availability-zone': 'placement', 'architecture': 'architecture', - 'image-id': 'image_id' + 'image-id': 'image_id', + 'network-interface.private-dns-name': 'private_dns', + 'private-dns-name': 'private_dns' } diff --git a/moto/ecr/exceptions.py b/moto/ecr/exceptions.py new file mode 100644 index 000000000..f7b951b53 --- /dev/null +++ b/moto/ecr/exceptions.py @@ -0,0 +1,22 @@ +from __future__ import unicode_literals +from moto.core.exceptions import RESTError + + +class RepositoryNotFoundException(RESTError): + code = 400 + + def __init__(self, repository_name, registry_id): + super(RepositoryNotFoundException, self).__init__( + error_type="RepositoryNotFoundException", + message="The repository with name '{0}' does not exist in the registry " + "with id '{1}'".format(repository_name, registry_id)) + + +class ImageNotFoundException(RESTError): + code = 400 + + def __init__(self, image_id, repository_name, registry_id): + super(ImageNotFoundException, self).__init__( + error_type="ImageNotFoundException", + message="The image with imageId {0} does not exist within the repository with name '{1}' " + "in the registry with id '{2}'".format(image_id, repository_name, registry_id)) diff --git a/moto/ecr/models.py b/moto/ecr/models.py index b90700ff4..f5b6f24e4 100644 --- a/moto/ecr/models.py +++ b/moto/ecr/models.py @@ -7,6 +7,11 @@ from moto.ec2 import ec2_backends from copy import copy import hashlib +from moto.ecr.exceptions import ImageNotFoundException, RepositoryNotFoundException + + +DEFAULT_REGISTRY_ID = '012345678910' + class BaseObject(BaseModel): @@ -35,14 +40,13 @@ class BaseObject(BaseModel): class Repository(BaseObject): def __init__(self, repository_name): - self.arn = 'arn:aws:ecr:us-east-1:012345678910:repository/{0}'.format( - repository_name) + self.registry_id = DEFAULT_REGISTRY_ID + self.arn = 'arn:aws:ecr:us-east-1:{0}:repository/{1}'.format( + self.registry_id, repository_name) self.name = repository_name # self.created = datetime.utcnow() - self.uri = '012345678910.dkr.ecr.us-east-1.amazonaws.com/{0}'.format( - repository_name - ) - self.registry_id = '012345678910' + self.uri = '{0}.dkr.ecr.us-east-1.amazonaws.com/{1}'.format( + self.registry_id, repository_name) self.images = [] @property @@ -93,7 +97,7 @@ class Repository(BaseObject): class Image(BaseObject): - def __init__(self, tag, manifest, repository, registry_id="012345678910"): + def __init__(self, tag, manifest, repository, registry_id=DEFAULT_REGISTRY_ID): self.image_tag = tag self.image_manifest = manifest self.image_size_in_bytes = 50 * 1024 * 1024 @@ -151,6 +155,11 @@ class ECRBackend(BaseBackend): """ maxResults and nextToken not implemented """ + if repository_names: + for repository_name in repository_names: + if repository_name not in self.repositories: + raise RepositoryNotFoundException(repository_name, registry_id or DEFAULT_REGISTRY_ID) + repositories = [] for repository in self.repositories.values(): # If a registry_id was supplied, ensure this repository matches @@ -170,11 +179,11 @@ class ECRBackend(BaseBackend): self.repositories[repository_name] = repository return repository - def delete_repository(self, respository_name, registry_id=None): - if respository_name in self.repositories: - return self.repositories.pop(respository_name) + def delete_repository(self, repository_name, registry_id=None): + if repository_name in self.repositories: + return self.repositories.pop(repository_name) else: - raise Exception("{0} is not a repository".format(respository_name)) + raise RepositoryNotFoundException(repository_name, registry_id or DEFAULT_REGISTRY_ID) def list_images(self, repository_name, registry_id=None): """ @@ -198,17 +207,27 @@ class ECRBackend(BaseBackend): if repository_name in self.repositories: repository = self.repositories[repository_name] else: - raise Exception("{0} is not a repository".format(repository_name)) + raise RepositoryNotFoundException(repository_name, registry_id or DEFAULT_REGISTRY_ID) if image_ids: response = set() for image_id in image_ids: - if 'imageDigest' in image_id: - desired_digest = image_id['imageDigest'] - response.update([i for i in repository.images if i.get_image_digest() == desired_digest]) - if 'imageTag' in image_id: - desired_tag = image_id['imageTag'] - response.update([i for i in repository.images if i.image_tag == desired_tag]) + found = False + for image in repository.images: + if (('imageDigest' in image_id and image.get_image_digest() == image_id['imageDigest']) or + ('imageTag' in image_id and image.image_tag == image_id['imageTag'])): + found = True + response.add(image) + if not found: + image_id_representation = "{imageDigest:'%s', imageTag:'%s'}" % ( + image_id.get('imageDigest', 'null'), + image_id.get('imageTag', 'null'), + ) + raise ImageNotFoundException( + image_id=image_id_representation, + repository_name=repository_name, + registry_id=registry_id or DEFAULT_REGISTRY_ID) + else: response = [] for image in repository.images: diff --git a/moto/ecr/responses.py b/moto/ecr/responses.py index 4fa0946b8..6207de4eb 100644 --- a/moto/ecr/responses.py +++ b/moto/ecr/responses.py @@ -45,7 +45,8 @@ class ECRResponse(BaseResponse): def delete_repository(self): repository_str = self._get_param('repositoryName') - repository = self.ecr_backend.delete_repository(repository_str) + registry_id = self._get_param('registryId') + repository = self.ecr_backend.delete_repository(repository_str, registry_id) return json.dumps({ 'repository': repository.response_object }) diff --git a/moto/ecs/models.py b/moto/ecs/models.py index d3ec2b7f7..bc847b32e 100644 --- a/moto/ecs/models.py +++ b/moto/ecs/models.py @@ -114,7 +114,7 @@ class TaskDefinition(BaseObject): family = properties.get( 'Family', 'task-definition-{0}'.format(int(random() * 10 ** 6))) container_definitions = properties['ContainerDefinitions'] - volumes = properties['Volumes'] + volumes = properties.get('Volumes') ecs_backend = ecs_backends[region_name] return ecs_backend.register_task_definition( @@ -127,7 +127,7 @@ class TaskDefinition(BaseObject): family = properties.get( 'Family', 'task-definition-{0}'.format(int(random() * 10 ** 6))) container_definitions = properties['ContainerDefinitions'] - volumes = properties['Volumes'] + volumes = properties.get('Volumes') if (original_resource.family != family or original_resource.container_definitions != container_definitions or original_resource.volumes != volumes): diff --git a/moto/ecs/responses.py b/moto/ecs/responses.py index 50d9e3cd4..8f6fe850f 100644 --- a/moto/ecs/responses.py +++ b/moto/ecs/responses.py @@ -18,8 +18,8 @@ class EC2ContainerServiceResponse(BaseResponse): except ValueError: return {} - def _get_param(self, param): - return self.request_params.get(param, None) + def _get_param(self, param, if_none=None): + return self.request_params.get(param, if_none) def create_cluster(self): cluster_name = self._get_param('clusterName') diff --git a/moto/elbv2/exceptions.py b/moto/elbv2/exceptions.py index 397aa115b..0947535eb 100644 --- a/moto/elbv2/exceptions.py +++ b/moto/elbv2/exceptions.py @@ -42,7 +42,7 @@ class SubnetNotFoundError(ELBClientError): class TargetGroupNotFoundError(ELBClientError): def __init__(self): - super(TooManyTagsError, self).__init__( + super(TargetGroupNotFoundError, self).__init__( "TargetGroupNotFound", "The specified target group does not exist.") @@ -101,3 +101,85 @@ class EmptyListenersError(ELBClientError): super(EmptyListenersError, self).__init__( "ValidationError", "Listeners cannot be empty") + + +class PriorityInUseError(ELBClientError): + + def __init__(self): + super(PriorityInUseError, self).__init__( + "PriorityInUse", + "The specified priority is in use.") + + +class InvalidConditionFieldError(ELBClientError): + + def __init__(self, invalid_name): + super(InvalidConditionFieldError, self).__init__( + "ValidationError", + "Condition field '%s' must be one of '[path-pattern, host-header]" % (invalid_name)) + + +class InvalidConditionValueError(ELBClientError): + + def __init__(self, msg): + super(InvalidConditionValueError, self).__init__( + "ValidationError", msg) + + +class InvalidActionTypeError(ELBClientError): + + def __init__(self, invalid_name, index): + super(InvalidActionTypeError, self).__init__( + "ValidationError", + "1 validation error detected: Value '%s' at 'actions.%s.member.type' failed to satisfy constraint: Member must satisfy enum value set: [forward]" % (invalid_name, index) + ) + + +class ActionTargetGroupNotFoundError(ELBClientError): + + def __init__(self, arn): + super(ActionTargetGroupNotFoundError, self).__init__( + "TargetGroupNotFound", + "Target group '%s' not found" % arn + ) + + +class InvalidDescribeRulesRequest(ELBClientError): + + def __init__(self, msg): + super(InvalidDescribeRulesRequest, self).__init__( + "ValidationError", msg + ) + + +class RuleNotFoundError(ELBClientError): + + def __init__(self): + super(RuleNotFoundError, self).__init__( + "RuleNotFound", + "The specified rule does not exist.") + + +class DuplicatePriorityError(ELBClientError): + + def __init__(self, invalid_value): + super(DuplicatePriorityError, self).__init__( + "ValidationError", + "Priority '%s' was provided multiple times" % invalid_value) + + +class InvalidTargetGroupNameError(ELBClientError): + + def __init__(self, msg): + super(InvalidTargetGroupNameError, self).__init__( + "ValidationError", msg + ) + + +class InvalidModifyRuleArgumentsError(ELBClientError): + + def __init__(self): + super(InvalidModifyRuleArgumentsError, self).__init__( + "ValidationError", + "Either conditions or actions must be specified" + ) diff --git a/moto/elbv2/models.py b/moto/elbv2/models.py index 10d9ad220..3c6afe7f5 100644 --- a/moto/elbv2/models.py +++ b/moto/elbv2/models.py @@ -1,6 +1,7 @@ from __future__ import unicode_literals import datetime +import re from moto.compat import OrderedDict from moto.core import BaseBackend, BaseModel from moto.ec2.models import ec2_backends @@ -14,6 +15,16 @@ from .exceptions import ( SubnetNotFoundError, TargetGroupNotFoundError, TooManyTagsError, + PriorityInUseError, + InvalidConditionFieldError, + InvalidConditionValueError, + InvalidActionTypeError, + ActionTargetGroupNotFoundError, + InvalidDescribeRulesRequest, + RuleNotFoundError, + DuplicatePriorityError, + InvalidTargetGroupNameError, + InvalidModifyRuleArgumentsError ) @@ -54,6 +65,7 @@ class FakeTargetGroup(BaseModel): self.healthy_threshold_count = healthy_threshold_count self.unhealthy_threshold_count = unhealthy_threshold_count self.load_balancer_arns = [] + self.tags = {} self.attributes = { 'deregistration_delay.timeout_seconds': 300, @@ -71,10 +83,15 @@ class FakeTargetGroup(BaseModel): def deregister(self, targets): for target in targets: - t = self.targets.pop(target['id']) + t = self.targets.pop(target['id'], None) if not t: raise InvalidTargetError() + def add_tag(self, key, value): + if len(self.tags) >= 10 and key not in self.tags: + raise TooManyTagsError() + self.tags[key] = value + def health_for(self, target): t = self.targets.get(target['id']) if t is None: @@ -92,6 +109,36 @@ class FakeListener(BaseModel): self.ssl_policy = ssl_policy self.certificate = certificate self.default_actions = default_actions + self._non_default_rules = [] + self._default_rule = FakeRule( + listener_arn=self.arn, + conditions=[], + priority='default', + actions=default_actions, + is_default=True + ) + + @property + def rules(self): + return self._non_default_rules + [self._default_rule] + + def remove_rule(self, rule): + self._non_default_rules.remove(rule) + + def register(self, rule): + self._non_default_rules.append(rule) + self._non_default_rules = sorted(self._non_default_rules, key=lambda x: x.priority) + + +class FakeRule(BaseModel): + + def __init__(self, listener_arn, conditions, priority, actions, is_default): + self.listener_arn = listener_arn + self.arn = listener_arn.replace(':listener/', ':listener-rule/') + "/%s" % (id(self)) + self.conditions = conditions + self.priority = priority # int or 'default' + self.actions = actions + self.is_default = is_default class FakeBackend(BaseModel): @@ -181,7 +228,73 @@ class ELBv2Backend(BaseBackend): self.load_balancers[arn] = new_load_balancer return new_load_balancer + def create_rule(self, listener_arn, conditions, priority, actions): + listeners = self.describe_listeners(None, [listener_arn]) + if not listeners: + raise ListenerNotFoundError() + listener = listeners[0] + + # validate conditions + for condition in conditions: + field = condition['field'] + if field not in ['path-pattern', 'host-header']: + raise InvalidConditionFieldError(field) + + values = condition['values'] + if len(values) == 0: + raise InvalidConditionValueError('A condition value must be specified') + if len(values) > 1: + raise InvalidConditionValueError( + "The '%s' field contains too many values; the limit is '1'" % field + ) + + # TODO: check pattern of value for 'host-header' + # TODO: check pattern of value for 'path-pattern' + + # validate Priority + for rule in listener.rules: + if rule.priority == priority: + raise PriorityInUseError() + + # validate Actions + target_group_arns = [target_group.arn for target_group in self.target_groups.values()] + for i, action in enumerate(actions): + index = i + 1 + action_type = action['type'] + if action_type not in ['forward']: + raise InvalidActionTypeError(action_type, index) + action_target_group_arn = action['target_group_arn'] + if action_target_group_arn not in target_group_arns: + raise ActionTargetGroupNotFoundError(action_target_group_arn) + + # TODO: check for error 'TooManyRegistrationsForTargetId' + # TODO: check for error 'TooManyRules' + + # create rule + rule = FakeRule(listener.arn, conditions, priority, actions, is_default=False) + listener.register(rule) + return [rule] + def create_target_group(self, name, **kwargs): + if len(name) > 32: + raise InvalidTargetGroupNameError( + "Target group name '%s' cannot be longer than '32' characters" % name + ) + if not re.match('^[a-zA-Z0-9\-]+$', name): + raise InvalidTargetGroupNameError( + "Target group name '%s' can only contain characters that are alphanumeric characters or hyphens(-)" % name + ) + + # undocumented validation + if not re.match('(?!.*--)(?!^-)(?!.*-$)^[A-Za-z0-9-]+$', name): + raise InvalidTargetGroupNameError( + "1 validation error detected: Value '%s' at 'targetGroup.targetGroupArn.targetGroupName' failed to satisfy constraint: Member must satisfy regular expression pattern: (?!.*--)(?!^-)(?!.*-$)^[A-Za-z0-9-]+$" % name + ) + + if name.startswith('-') or name.endswith('-'): + raise InvalidTargetGroupNameError( + "Target group name '%s' cannot begin or end with '-'" % name + ) for target_group in self.target_groups.values(): if target_group.name == name: raise DuplicateTargetGroupName() @@ -233,6 +346,29 @@ class ELBv2Backend(BaseBackend): return matched_balancers + def describe_rules(self, listener_arn, rule_arns): + if listener_arn is None and not rule_arns: + raise InvalidDescribeRulesRequest( + "You must specify either listener rule ARNs or a listener ARN" + ) + if listener_arn is not None and rule_arns is not None: + raise InvalidDescribeRulesRequest( + 'Listener rule ARNs and a listener ARN cannot be specified at the same time' + ) + if listener_arn: + listener = self.describe_listeners(None, [listener_arn])[0] + return listener.rules + + # search for rule arns + matched_rules = [] + for load_balancer_arn in self.load_balancers: + listeners = self.load_balancers.get(load_balancer_arn).listeners.values() + for listener in listeners: + for rule in listener.rules: + if rule.arn in rule_arns: + matched_rules.append(rule) + return matched_rules + def describe_target_groups(self, load_balancer_arn, target_group_arns, names): if load_balancer_arn: if load_balancer_arn not in self.load_balancers: @@ -249,7 +385,7 @@ class ELBv2Backend(BaseBackend): matched = [] for name in names: found = None - for target_group in self.target_groups: + for target_group in self.target_groups.values(): if target_group.name == name: found = target_group if not found: @@ -277,19 +413,78 @@ class ELBv2Backend(BaseBackend): def delete_load_balancer(self, arn): self.load_balancers.pop(arn, None) + def delete_rule(self, arn): + for load_balancer_arn in self.load_balancers: + listeners = self.load_balancers.get(load_balancer_arn).listeners.values() + for listener in listeners: + for rule in listener.rules: + if rule.arn == arn: + listener.remove_rule(rule) + return + + # should raise RuleNotFound Error according to the AWS API doc + # however, boto3 does't raise error even if rule is not found + def delete_target_group(self, target_group_arn): - target_group = self.target_groups.pop(target_group_arn) + target_group = self.target_groups.pop(target_group_arn, None) if target_group: return target_group raise TargetGroupNotFoundError() def delete_listener(self, listener_arn): for load_balancer in self.load_balancers.values(): - listener = load_balancer.listeners.pop(listener_arn) + listener = load_balancer.listeners.pop(listener_arn, None) if listener: return listener raise ListenerNotFoundError() + def modify_rule(self, rule_arn, conditions, actions): + # if conditions or actions is empty list, do not update the attributes + if not conditions and not actions: + raise InvalidModifyRuleArgumentsError() + rules = self.describe_rules(listener_arn=None, rule_arns=[rule_arn]) + if not rules: + raise RuleNotFoundError() + rule = rules[0] + + if conditions: + for condition in conditions: + field = condition['field'] + if field not in ['path-pattern', 'host-header']: + raise InvalidConditionFieldError(field) + + values = condition['values'] + if len(values) == 0: + raise InvalidConditionValueError('A condition value must be specified') + if len(values) > 1: + raise InvalidConditionValueError( + "The '%s' field contains too many values; the limit is '1'" % field + ) + # TODO: check pattern of value for 'host-header' + # TODO: check pattern of value for 'path-pattern' + + # validate Actions + target_group_arns = [target_group.arn for target_group in self.target_groups.values()] + if actions: + for i, action in enumerate(actions): + index = i + 1 + action_type = action['type'] + if action_type not in ['forward']: + raise InvalidActionTypeError(action_type, index) + action_target_group_arn = action['target_group_arn'] + if action_target_group_arn not in target_group_arns: + raise ActionTargetGroupNotFoundError(action_target_group_arn) + + # TODO: check for error 'TooManyRegistrationsForTargetId' + # TODO: check for error 'TooManyRules' + + # modify rule + if conditions: + rule.conditions = conditions + if actions: + rule.actions = actions + return [rule] + def register_targets(self, target_group_arn, instances): target_group = self.target_groups.get(target_group_arn) if target_group is None: @@ -311,6 +506,39 @@ class ELBv2Backend(BaseBackend): targets = target_group.targets.values() return [target_group.health_for(target) for target in targets] + def set_rule_priorities(self, rule_priorities): + # validate + priorities = [rule_priority['priority'] for rule_priority in rule_priorities] + for priority in set(priorities): + if priorities.count(priority) > 1: + raise DuplicatePriorityError(priority) + + # validate + for rule_priority in rule_priorities: + given_rule_arn = rule_priority['rule_arn'] + priority = rule_priority['priority'] + _given_rules = self.describe_rules(listener_arn=None, rule_arns=[given_rule_arn]) + if not _given_rules: + raise RuleNotFoundError() + given_rule = _given_rules[0] + listeners = self.describe_listeners(None, [given_rule.listener_arn]) + listener = listeners[0] + for rule_in_listener in listener.rules: + if rule_in_listener.priority == priority: + raise PriorityInUseError() + # modify + modified_rules = [] + for rule_priority in rule_priorities: + given_rule_arn = rule_priority['rule_arn'] + priority = rule_priority['priority'] + _given_rules = self.describe_rules(listener_arn=None, rule_arns=[given_rule_arn]) + if not _given_rules: + raise RuleNotFoundError() + given_rule = _given_rules[0] + given_rule.priority = priority + modified_rules.append(given_rule) + return modified_rules + elbv2_backends = {} for region in ec2_backends.keys(): diff --git a/moto/elbv2/responses.py b/moto/elbv2/responses.py index 751652901..3e8535187 100644 --- a/moto/elbv2/responses.py +++ b/moto/elbv2/responses.py @@ -28,6 +28,30 @@ class ELBV2Response(BaseResponse): template = self.response_template(CREATE_LOAD_BALANCER_TEMPLATE) return template.render(load_balancer=load_balancer) + def create_rule(self): + lister_arn = self._get_param('ListenerArn') + _conditions = self._get_list_prefix('Conditions.member') + conditions = [] + for _condition in _conditions: + condition = {} + condition['field'] = _condition['field'] + values = sorted( + [e for e in _condition.items() if e[0].startswith('values.member')], + key=lambda x: x[0] + ) + condition['values'] = [e[1] for e in values] + conditions.append(condition) + priority = self._get_int_param('Priority') + actions = self._get_list_prefix('Actions.member') + rules = self.elbv2_backend.create_rule( + listener_arn=lister_arn, + conditions=conditions, + priority=priority, + actions=actions + ) + template = self.response_template(CREATE_RULE_TEMPLATE) + return template.render(rules=rules) + def create_target_group(self): name = self._get_param('Name') vpc_id = self._get_param('VpcId') @@ -100,6 +124,26 @@ class ELBV2Response(BaseResponse): template = self.response_template(DESCRIBE_LOAD_BALANCERS_TEMPLATE) return template.render(load_balancers=load_balancers_resp, marker=next_marker) + def describe_rules(self): + listener_arn = self._get_param('ListenerArn') + rule_arns = self._get_multi_param('RuleArns.member') if any(k for k in list(self.querystring.keys()) if k.startswith('RuleArns.member')) else None + all_rules = self.elbv2_backend.describe_rules(listener_arn, rule_arns) + all_arns = [rule.arn for rule in all_rules] + page_size = self._get_int_param('PageSize', 50) # set 50 for temporary + + marker = self._get_param('Marker') + if marker: + start = all_arns.index(marker) + 1 + else: + start = 0 + rules_resp = all_rules[start:start + page_size] + next_marker = None + + if len(all_rules) > start + page_size: + next_marker = rules_resp[-1].arn + template = self.response_template(DESCRIBE_RULES_TEMPLATE) + return template.render(rules=rules_resp, marker=next_marker) + def describe_target_groups(self): load_balancer_arn = self._get_param('LoadBalancerArn') target_group_arns = self._get_multi_param('TargetGroupArns.member') @@ -133,6 +177,12 @@ class ELBV2Response(BaseResponse): template = self.response_template(DELETE_LOAD_BALANCER_TEMPLATE) return template.render() + def delete_rule(self): + arn = self._get_param('RuleArn') + self.elbv2_backend.delete_rule(arn) + template = self.response_template(DELETE_RULE_TEMPLATE) + return template.render() + def delete_target_group(self): arn = self._get_param('TargetGroupArn') self.elbv2_backend.delete_target_group(arn) @@ -145,6 +195,28 @@ class ELBV2Response(BaseResponse): template = self.response_template(DELETE_LISTENER_TEMPLATE) return template.render() + def modify_rule(self): + rule_arn = self._get_param('RuleArn') + _conditions = self._get_list_prefix('Conditions.member') + conditions = [] + for _condition in _conditions: + condition = {} + condition['field'] = _condition['field'] + values = sorted( + [e for e in _condition.items() if e[0].startswith('values.member')], + key=lambda x: x[0] + ) + condition['values'] = [e[1] for e in values] + conditions.append(condition) + actions = self._get_list_prefix('Actions.member') + rules = self.elbv2_backend.modify_rule( + rule_arn=rule_arn, + conditions=conditions, + actions=actions + ) + template = self.response_template(MODIFY_RULE_TEMPLATE) + return template.render(rules=rules) + def modify_target_group_attributes(self): target_group_arn = self._get_param('TargetGroupArn') target_group = self.elbv2_backend.target_groups.get(target_group_arn) @@ -182,14 +254,29 @@ class ELBV2Response(BaseResponse): template = self.response_template(DESCRIBE_TARGET_HEALTH_TEMPLATE) return template.render(target_health_descriptions=target_health_descriptions) + def set_rule_priorities(self): + rule_priorities = self._get_list_prefix('RulePriorities.member') + for rule_priority in rule_priorities: + rule_priority['priority'] = int(rule_priority['priority']) + rules = self.elbv2_backend.set_rule_priorities(rule_priorities) + template = self.response_template(SET_RULE_PRIORITIES_TEMPLATE) + return template.render(rules=rules) + def add_tags(self): resource_arns = self._get_multi_param('ResourceArns.member') for arn in resource_arns: - load_balancer = self.elbv2_backend.load_balancers.get(arn) - if not load_balancer: + if ':targetgroup' in arn: + resource = self.elbv2_backend.target_groups.get(arn) + if not resource: + raise TargetGroupNotFoundError() + elif ':loadbalancer' in arn: + resource = self.elbv2_backend.load_balancers.get(arn) + if not resource: + raise LoadBalancerNotFoundError() + else: raise LoadBalancerNotFoundError() - self._add_tags(load_balancer) + self._add_tags(resource) template = self.response_template(ADD_TAGS_TEMPLATE) return template.render() @@ -199,30 +286,41 @@ class ELBV2Response(BaseResponse): tag_keys = self._get_multi_param('TagKeys.member') for arn in resource_arns: - load_balancer = self.elbv2_backend.load_balancers.get(arn) - if not load_balancer: + if ':targetgroup' in arn: + resource = self.elbv2_backend.target_groups.get(arn) + if not resource: + raise TargetGroupNotFoundError() + elif ':loadbalancer' in arn: + resource = self.elbv2_backend.load_balancers.get(arn) + if not resource: + raise LoadBalancerNotFoundError() + else: raise LoadBalancerNotFoundError() - [load_balancer.remove_tag(key) for key in tag_keys] + [resource.remove_tag(key) for key in tag_keys] template = self.response_template(REMOVE_TAGS_TEMPLATE) return template.render() def describe_tags(self): - elbs = [] - for key, value in self.querystring.items(): - if "ResourceArns.member" in key: - number = key.split('.')[2] - load_balancer_arn = self._get_param( - 'ResourceArns.member.{0}'.format(number)) - elb = self.elbv2_backend.load_balancers.get(load_balancer_arn) - if not elb: + resource_arns = self._get_multi_param('ResourceArns.member') + resources = [] + for arn in resource_arns: + if ':targetgroup' in arn: + resource = self.elbv2_backend.target_groups.get(arn) + if not resource: + raise TargetGroupNotFoundError() + elif ':loadbalancer' in arn: + resource = self.elbv2_backend.load_balancers.get(arn) + if not resource: raise LoadBalancerNotFoundError() - elbs.append(elb) + else: + raise LoadBalancerNotFoundError() + resources.append(resource) template = self.response_template(DESCRIBE_TAGS_TEMPLATE) - return template.render(load_balancers=elbs) + return template.render(resources=resources) - def _add_tags(self, elb): + def _add_tags(self, resource): tag_values = [] tag_keys = [] @@ -244,7 +342,7 @@ class ELBV2Response(BaseResponse): raise DuplicateTagKeysError(counts[0]) for tag_key, tag_value in zip(tag_keys, tag_values): - elb.add_tag(tag_key, tag_value) + resource.add_tag(tag_key, tag_value) ADD_TAGS_TEMPLATE = """ @@ -264,11 +362,11 @@ REMOVE_TAGS_TEMPLATE = """ - {% for load_balancer in load_balancers %} + {% for resource in resources %} - {{ load_balancer.arn }} + {{ resource.arn }} - {% for key, value in load_balancer.tags.items() %} + {% for key, value in resource.tags.items() %} {{ value }} {{ key }} @@ -321,6 +419,43 @@ CREATE_LOAD_BALANCER_TEMPLATE = """ + + + {% for rule in rules %} + + {{ "true" if rule.is_default else "false" }} + + {% for condition in rule.conditions %} + + {{ condition["field"] }} + + {% for value in condition["values"] %} + {{ value }} + {% endfor %} + + + {% endfor %} + + {{ rule.priority }} + + {% for action in rule.actions %} + + {{ action["type"] }} + {{ action["target_group_arn"] }} + + {% endfor %} + + {{ rule.arn }} + + {% endfor %} + + + + c5478c83-f397-11e5-bb98-57195a6eb84a + +""" + CREATE_TARGET_GROUP_TEMPLATE = """ @@ -387,6 +522,13 @@ DELETE_LOAD_BALANCER_TEMPLATE = """ + + + 1549581b-12b7-11e3-895e-1334aEXAMPLE + +""" + DELETE_TARGET_GROUP_TEMPLATE = """ @@ -442,6 +584,45 @@ DESCRIBE_LOAD_BALANCERS_TEMPLATE = """ + + + {% for rule in rules %} + + {{ "true" if rule.is_default else "false" }} + + {% for condition in rule.conditions %} + + {{ condition["field"] }} + + {% for value in condition["values"] %} + {{ value }} + {% endfor %} + + + {% endfor %} + + {{ rule.priority }} + + {% for action in rule.actions %} + + {{ action["type"] }} + {{ action["target_group_arn"] }} + + {% endfor %} + + {{ rule.arn }} + + {% endfor %} + + {% if marker %} + {{ marker }} + {% endif %} + + + 74926cf3-f3a3-11e5-b543-9f2c3fbb9bee + +""" DESCRIBE_TARGET_GROUPS_TEMPLATE = """ @@ -505,7 +686,7 @@ DESCRIBE_LISTENERS_TEMPLATE = """ + + + {% for rule in rules %} + + {{ "true" if rule.is_default else "false" }} + + {% for condition in rule.conditions %} + + {{ condition["field"] }} + + {% for value in condition["values"] %} + {{ value }} + {% endfor %} + + + {% endfor %} + + {{ rule.priority }} + + {% for action in rule.actions %} + + {{ action["type"] }} + {{ action["target_group_arn"] }} + + {% endfor %} + + {{ rule.arn }} + + {% endfor %} + + + + c5478c83-f397-11e5-bb98-57195a6eb84a + +""" + MODIFY_TARGET_GROUP_ATTRIBUTES_TEMPLATE = """ @@ -703,3 +921,40 @@ DESCRIBE_TARGET_HEALTH_TEMPLATE = """ + + + {% for rule in rules %} + + {{ "true" if rule.is_default else "false" }} + + {% for condition in rule.conditions %} + + {{ condition["field"] }} + + {% for value in condition["values"] %} + {{ value }} + {% endfor %} + + + {% endfor %} + + {{ rule.priority }} + + {% for action in rule.actions %} + + {{ action["type"] }} + {{ action["target_group_arn"] }} + + {% endfor %} + + {{ rule.arn }} + + {% endfor %} + + + + 4d7a8036-f3a7-11e5-9c02-8fd20490d5a6 + +""" diff --git a/moto/iam/aws_managed_policies.py b/moto/iam/aws_managed_policies.py new file mode 100644 index 000000000..df348c0d9 --- /dev/null +++ b/moto/iam/aws_managed_policies.py @@ -0,0 +1,12944 @@ +# Imported via `make aws_managed_policies` +aws_managed_policies_data = """ +{ + "AWSAccountActivityAccess": { + "Arn": "arn:aws:iam::aws:policy/AWSAccountActivityAccess", + "AttachmentCount": 0, + "CreateDate": "2015-02-06T18:41:18+00:00", + "DefaultVersionId": "v1", + "Document": { + "Statement": [ + { + "Action": [ + "aws-portal:ViewBilling" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAJQRYCWMFX5J3E333K", + "PolicyName": "AWSAccountActivityAccess", + "UpdateDate": "2015-02-06T18:41:18+00:00", + "VersionId": "v1" + }, + "AWSAccountUsageReportAccess": { + "Arn": "arn:aws:iam::aws:policy/AWSAccountUsageReportAccess", + "AttachmentCount": 0, + "CreateDate": "2015-02-06T18:41:19+00:00", + "DefaultVersionId": "v1", + "Document": { + "Statement": [ + { + "Action": [ + "aws-portal:ViewUsage" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAJLIB4VSBVO47ZSBB6", + "PolicyName": "AWSAccountUsageReportAccess", + "UpdateDate": "2015-02-06T18:41:19+00:00", + "VersionId": "v1" + }, + "AWSAgentlessDiscoveryService": { + "Arn": "arn:aws:iam::aws:policy/AWSAgentlessDiscoveryService", + "AttachmentCount": 0, + "CreateDate": "2016-08-02T01:35:11+00:00", + "DefaultVersionId": "v1", + "Document": { + "Statement": [ + { + "Action": [ + "awsconnector:RegisterConnector", + "awsconnector:GetConnectorHealth" + ], + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": "iam:GetUser", + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": [ + "s3:GetObject", + "s3:ListBucket" + ], + "Effect": "Allow", + "Resource": [ + "arn:aws:s3:::connector-platform-upgrade-info/*", + "arn:aws:s3:::connector-platform-upgrade-info", + "arn:aws:s3:::connector-platform-upgrade-bundles/*", + "arn:aws:s3:::connector-platform-upgrade-bundles", + "arn:aws:s3:::connector-platform-release-notes/*", + "arn:aws:s3:::connector-platform-release-notes", + "arn:aws:s3:::prod.agentless.discovery.connector.upgrade/*", + "arn:aws:s3:::prod.agentless.discovery.connector.upgrade" + ] + }, + { + "Action": [ + "s3:PutObject", + "s3:PutObjectAcl" + ], + "Effect": "Allow", + "Resource": [ + "arn:aws:s3:::import-to-ec2-connector-debug-logs/*" + ] + }, + { + "Action": [ + "SNS:Publish" + ], + "Effect": "Allow", + "Resource": "arn:aws:sns:*:*:metrics-sns-topic-for-*" + }, + { + "Action": [ + "Discovery:*" + ], + "Effect": "Allow", + "Resource": "*", + "Sid": "Discovery" + }, + { + "Action": [ + "arsenal:RegisterOnPremisesAgent" + ], + "Effect": "Allow", + "Resource": "*", + "Sid": "arsenal" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAIA3DIL7BYQ35ISM4K", + "PolicyName": "AWSAgentlessDiscoveryService", + "UpdateDate": "2016-08-02T01:35:11+00:00", + "VersionId": "v1" + }, + "AWSApplicationDiscoveryAgentAccess": { + "Arn": "arn:aws:iam::aws:policy/AWSApplicationDiscoveryAgentAccess", + "AttachmentCount": 0, + "CreateDate": "2016-05-11T21:38:47+00:00", + "DefaultVersionId": "v1", + "Document": { + "Statement": [ + { + "Action": [ + "arsenal:RegisterOnPremisesAgent" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAICZIOVAGC6JPF3WHC", + "PolicyName": "AWSApplicationDiscoveryAgentAccess", + "UpdateDate": "2016-05-11T21:38:47+00:00", + "VersionId": "v1" + }, + "AWSApplicationDiscoveryServiceFullAccess": { + "Arn": "arn:aws:iam::aws:policy/AWSApplicationDiscoveryServiceFullAccess", + "AttachmentCount": 0, + "CreateDate": "2016-05-11T21:30:50+00:00", + "DefaultVersionId": "v1", + "Document": { + "Statement": [ + { + "Action": "discovery:*", + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAJBNJEA6ZXM2SBOPDU", + "PolicyName": "AWSApplicationDiscoveryServiceFullAccess", + "UpdateDate": "2016-05-11T21:30:50+00:00", + "VersionId": "v1" + }, + "AWSBatchFullAccess": { + "Arn": "arn:aws:iam::aws:policy/AWSBatchFullAccess", + "AttachmentCount": 0, + "CreateDate": "2016-12-13T00:38:59+00:00", + "DefaultVersionId": "v2", + "Document": { + "Statement": [ + { + "Action": [ + "batch:*", + "cloudwatch:GetMetricStatistics", + "ec2:DescribeSubnets", + "ec2:DescribeSecurityGroups", + "ec2:DescribeKeyPairs", + "ecs:DescribeClusters", + "ecs:Describe*", + "ecs:List*", + "logs:Describe*", + "logs:Get*", + "logs:TestMetricFilter", + "logs:FilterLogEvents", + "iam:ListInstanceProfiles", + "iam:ListRoles" + ], + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": [ + "iam:PassRole" + ], + "Effect": "Allow", + "Resource": [ + "arn:aws:iam::*:role/AWSBatchServiceRole", + "arn:aws:iam::*:role/ecsInstanceRole", + "arn:aws:iam::*:role/iaws-ec2-spot-fleet-role", + "arn:aws:iam::*:role/aws-ec2-spot-fleet-role", + "arn:aws:iam::*:role/AWSBatchJobRole*" + ] + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAJ7K2KIWB3HZVK3CUO", + "PolicyName": "AWSBatchFullAccess", + "UpdateDate": "2016-12-13T00:38:59+00:00", + "VersionId": "v2" + }, + "AWSBatchServiceRole": { + "Arn": "arn:aws:iam::aws:policy/service-role/AWSBatchServiceRole", + "AttachmentCount": 0, + "CreateDate": "2017-05-11T20:44:52+00:00", + "DefaultVersionId": "v4", + "Document": { + "Statement": [ + { + "Action": [ + "ec2:DescribeAccountAttributes", + "ec2:DescribeInstances", + "ec2:DescribeSubnets", + "ec2:DescribeSecurityGroups", + "ec2:DescribeKeyPairs", + "ec2:DescribeImages", + "ec2:DescribeImageAttribute", + "ec2:DescribeSpotFleetInstances", + "ec2:DescribeSpotFleetRequests", + "ec2:DescribeSpotPriceHistory", + "ec2:RequestSpotFleet", + "ec2:CancelSpotFleetRequests", + "ec2:ModifySpotFleetRequest", + "ec2:TerminateInstances", + "autoscaling:DescribeAccountLimits", + "autoscaling:DescribeAutoScalingGroups", + "autoscaling:DescribeLaunchConfigurations", + "autoscaling:DescribeAutoScalingInstances", + "autoscaling:CreateLaunchConfiguration", + "autoscaling:CreateAutoScalingGroup", + "autoscaling:UpdateAutoScalingGroup", + "autoscaling:SetDesiredCapacity", + "autoscaling:DeleteLaunchConfiguration", + "autoscaling:DeleteAutoScalingGroup", + "autoscaling:CreateOrUpdateTags", + "autoscaling:SuspendProcesses", + "autoscaling:PutNotificationConfiguration", + "autoscaling:TerminateInstanceInAutoScalingGroup", + "ecs:DescribeClusters", + "ecs:DescribeContainerInstances", + "ecs:DescribeTaskDefinition", + "ecs:DescribeTasks", + "ecs:ListClusters", + "ecs:ListContainerInstances", + "ecs:ListTaskDefinitionFamilies", + "ecs:ListTaskDefinitions", + "ecs:ListTasks", + "ecs:CreateCluster", + "ecs:DeleteCluster", + "ecs:RegisterTaskDefinition", + "ecs:DeregisterTaskDefinition", + "ecs:RunTask", + "ecs:StartTask", + "ecs:StopTask", + "ecs:UpdateContainerAgent", + "ecs:DeregisterContainerInstance", + "logs:CreateLogGroup", + "logs:CreateLogStream", + "logs:PutLogEvents", + "logs:DescribeLogGroups", + "iam:GetInstanceProfile", + "iam:PassRole" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/service-role/", + "PolicyId": "ANPAIUETIXPCKASQJURFE", + "PolicyName": "AWSBatchServiceRole", + "UpdateDate": "2017-05-11T20:44:52+00:00", + "VersionId": "v4" + }, + "AWSCertificateManagerFullAccess": { + "Arn": "arn:aws:iam::aws:policy/AWSCertificateManagerFullAccess", + "AttachmentCount": 0, + "CreateDate": "2016-01-21T17:02:36+00:00", + "DefaultVersionId": "v1", + "Document": { + "Statement": [ + { + "Action": [ + "acm:*" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAJYCHABBP6VQIVBCBQ", + "PolicyName": "AWSCertificateManagerFullAccess", + "UpdateDate": "2016-01-21T17:02:36+00:00", + "VersionId": "v1" + }, + "AWSCertificateManagerReadOnly": { + "Arn": "arn:aws:iam::aws:policy/AWSCertificateManagerReadOnly", + "AttachmentCount": 0, + "CreateDate": "2016-04-21T15:08:16+00:00", + "DefaultVersionId": "v2", + "Document": { + "Statement": { + "Action": [ + "acm:DescribeCertificate", + "acm:ListCertificates", + "acm:GetCertificate", + "acm:ListTagsForCertificate" + ], + "Effect": "Allow", + "Resource": "*" + }, + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAI4GSWX6S4MESJ3EWC", + "PolicyName": "AWSCertificateManagerReadOnly", + "UpdateDate": "2016-04-21T15:08:16+00:00", + "VersionId": "v2" + }, + "AWSCloudFormationReadOnlyAccess": { + "Arn": "arn:aws:iam::aws:policy/AWSCloudFormationReadOnlyAccess", + "AttachmentCount": 0, + "CreateDate": "2015-02-06T18:39:49+00:00", + "DefaultVersionId": "v1", + "Document": { + "Statement": [ + { + "Action": [ + "cloudformation:DescribeStacks", + "cloudformation:DescribeStackEvents", + "cloudformation:DescribeStackResource", + "cloudformation:DescribeStackResources", + "cloudformation:GetTemplate", + "cloudformation:List*" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAJWVBEE4I2POWLODLW", + "PolicyName": "AWSCloudFormationReadOnlyAccess", + "UpdateDate": "2015-02-06T18:39:49+00:00", + "VersionId": "v1" + }, + "AWSCloudHSMFullAccess": { + "Arn": "arn:aws:iam::aws:policy/AWSCloudHSMFullAccess", + "AttachmentCount": 0, + "CreateDate": "2015-02-06T18:39:51+00:00", + "DefaultVersionId": "v1", + "Document": { + "Statement": [ + { + "Action": "cloudhsm:*", + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAIMBQYQZM7F63DA2UU", + "PolicyName": "AWSCloudHSMFullAccess", + "UpdateDate": "2015-02-06T18:39:51+00:00", + "VersionId": "v1" + }, + "AWSCloudHSMReadOnlyAccess": { + "Arn": "arn:aws:iam::aws:policy/AWSCloudHSMReadOnlyAccess", + "AttachmentCount": 0, + "CreateDate": "2015-02-06T18:39:52+00:00", + "DefaultVersionId": "v1", + "Document": { + "Statement": [ + { + "Action": [ + "cloudhsm:Get*", + "cloudhsm:List*", + "cloudhsm:Describe*" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAISVCBSY7YDBOT67KE", + "PolicyName": "AWSCloudHSMReadOnlyAccess", + "UpdateDate": "2015-02-06T18:39:52+00:00", + "VersionId": "v1" + }, + "AWSCloudHSMRole": { + "Arn": "arn:aws:iam::aws:policy/service-role/AWSCloudHSMRole", + "AttachmentCount": 0, + "CreateDate": "2015-02-06T18:41:23+00:00", + "DefaultVersionId": "v1", + "Document": { + "Statement": [ + { + "Action": [ + "ec2:CreateNetworkInterface", + "ec2:CreateTags", + "ec2:DeleteNetworkInterface", + "ec2:DescribeNetworkInterfaceAttribute", + "ec2:DescribeNetworkInterfaces", + "ec2:DescribeSubnets", + "ec2:DescribeVpcs", + "ec2:DetachNetworkInterface" + ], + "Effect": "Allow", + "Resource": [ + "*" + ] + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/service-role/", + "PolicyId": "ANPAI7QIUU4GC66SF26WE", + "PolicyName": "AWSCloudHSMRole", + "UpdateDate": "2015-02-06T18:41:23+00:00", + "VersionId": "v1" + }, + "AWSCloudTrailFullAccess": { + "Arn": "arn:aws:iam::aws:policy/AWSCloudTrailFullAccess", + "AttachmentCount": 0, + "CreateDate": "2016-02-16T18:31:28+00:00", + "DefaultVersionId": "v4", + "Document": { + "Statement": [ + { + "Action": [ + "sns:AddPermission", + "sns:CreateTopic", + "sns:DeleteTopic", + "sns:ListTopics", + "sns:SetTopicAttributes", + "sns:GetTopicAttributes" + ], + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": [ + "s3:CreateBucket", + "s3:DeleteBucket", + "s3:ListAllMyBuckets", + "s3:PutBucketPolicy", + "s3:ListBucket", + "s3:GetObject", + "s3:GetBucketLocation", + "s3:GetBucketPolicy" + ], + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": "cloudtrail:*", + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": [ + "logs:CreateLogGroup" + ], + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": [ + "iam:PassRole", + "iam:ListRoles", + "iam:GetRolePolicy", + "iam:GetUser" + ], + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": [ + "kms:ListKeys", + "kms:ListAliases" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAIQNUJTQYDRJPC3BNK", + "PolicyName": "AWSCloudTrailFullAccess", + "UpdateDate": "2016-02-16T18:31:28+00:00", + "VersionId": "v4" + }, + "AWSCloudTrailReadOnlyAccess": { + "Arn": "arn:aws:iam::aws:policy/AWSCloudTrailReadOnlyAccess", + "AttachmentCount": 0, + "CreateDate": "2016-12-14T20:41:52+00:00", + "DefaultVersionId": "v6", + "Document": { + "Statement": [ + { + "Action": [ + "s3:GetObject", + "s3:GetBucketLocation" + ], + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": [ + "cloudtrail:GetTrailStatus", + "cloudtrail:DescribeTrails", + "cloudtrail:LookupEvents", + "cloudtrail:ListTags", + "cloudtrail:ListPublicKeys", + "cloudtrail:GetEventSelectors", + "s3:ListAllMyBuckets", + "kms:ListAliases" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAJDU7KJADWBSEQ3E7S", + "PolicyName": "AWSCloudTrailReadOnlyAccess", + "UpdateDate": "2016-12-14T20:41:52+00:00", + "VersionId": "v6" + }, + "AWSCodeBuildAdminAccess": { + "Arn": "arn:aws:iam::aws:policy/AWSCodeBuildAdminAccess", + "AttachmentCount": 0, + "CreateDate": "2016-12-01T19:04:44+00:00", + "DefaultVersionId": "v1", + "Document": { + "Statement": [ + { + "Action": [ + "codebuild:*", + "codecommit:GetBranch", + "codecommit:GetCommit", + "codecommit:GetRepository", + "codecommit:ListBranches", + "codecommit:ListRepositories", + "ecr:DescribeRepositories", + "ecr:ListImages", + "s3:GetBucketLocation", + "s3:ListAllMyBuckets" + ], + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": [ + "logs:GetLogEvents" + ], + "Effect": "Allow", + "Resource": "arn:aws:logs:*:*:log-group:/aws/codebuild/*:log-stream:*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAJQJGIOIE3CD2TQXDS", + "PolicyName": "AWSCodeBuildAdminAccess", + "UpdateDate": "2016-12-01T19:04:44+00:00", + "VersionId": "v1" + }, + "AWSCodeBuildDeveloperAccess": { + "Arn": "arn:aws:iam::aws:policy/AWSCodeBuildDeveloperAccess", + "AttachmentCount": 0, + "CreateDate": "2016-12-01T19:02:32+00:00", + "DefaultVersionId": "v1", + "Document": { + "Statement": [ + { + "Action": [ + "codebuild:StartBuild", + "codebuild:StopBuild", + "codebuild:BatchGet*", + "codebuild:Get*", + "codebuild:List*", + "codecommit:GetBranch", + "codecommit:GetCommit", + "codecommit:GetRepository", + "codecommit:ListBranches", + "s3:GetBucketLocation", + "s3:ListAllMyBuckets" + ], + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": [ + "logs:GetLogEvents" + ], + "Effect": "Allow", + "Resource": "arn:aws:logs:*:*:log-group:/aws/codebuild/*:log-stream:*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAIMKTMR34XSBQW45HS", + "PolicyName": "AWSCodeBuildDeveloperAccess", + "UpdateDate": "2016-12-01T19:02:32+00:00", + "VersionId": "v1" + }, + "AWSCodeBuildReadOnlyAccess": { + "Arn": "arn:aws:iam::aws:policy/AWSCodeBuildReadOnlyAccess", + "AttachmentCount": 0, + "CreateDate": "2016-12-01T19:03:41+00:00", + "DefaultVersionId": "v1", + "Document": { + "Statement": [ + { + "Action": [ + "codebuild:BatchGet*", + "codebuild:Get*", + "codebuild:List*", + "codecommit:GetBranch", + "codecommit:GetCommit", + "codecommit:GetRepository" + ], + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": [ + "logs:GetLogEvents" + ], + "Effect": "Allow", + "Resource": "arn:aws:logs:*:*:log-group:/aws/codebuild/*:log-stream:*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAJIZZWN6557F5HVP2K", + "PolicyName": "AWSCodeBuildReadOnlyAccess", + "UpdateDate": "2016-12-01T19:03:41+00:00", + "VersionId": "v1" + }, + "AWSCodeCommitFullAccess": { + "Arn": "arn:aws:iam::aws:policy/AWSCodeCommitFullAccess", + "AttachmentCount": 0, + "CreateDate": "2015-07-09T17:02:19+00:00", + "DefaultVersionId": "v1", + "Document": { + "Statement": [ + { + "Action": [ + "codecommit:*" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAI4VCZ3XPIZLQ5NZV2", + "PolicyName": "AWSCodeCommitFullAccess", + "UpdateDate": "2015-07-09T17:02:19+00:00", + "VersionId": "v1" + }, + "AWSCodeCommitPowerUser": { + "Arn": "arn:aws:iam::aws:policy/AWSCodeCommitPowerUser", + "AttachmentCount": 0, + "CreateDate": "2017-05-22T21:12:48+00:00", + "DefaultVersionId": "v3", + "Document": { + "Statement": [ + { + "Action": [ + "codecommit:BatchGetRepositories", + "codecommit:CreateBranch", + "codecommit:CreateRepository", + "codecommit:DeleteBranch", + "codecommit:Get*", + "codecommit:GitPull", + "codecommit:GitPush", + "codecommit:List*", + "codecommit:Put*", + "codecommit:Test*", + "codecommit:Update*" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAI4UIINUVGB5SEC57G", + "PolicyName": "AWSCodeCommitPowerUser", + "UpdateDate": "2017-05-22T21:12:48+00:00", + "VersionId": "v3" + }, + "AWSCodeCommitReadOnly": { + "Arn": "arn:aws:iam::aws:policy/AWSCodeCommitReadOnly", + "AttachmentCount": 0, + "CreateDate": "2015-07-09T17:05:06+00:00", + "DefaultVersionId": "v1", + "Document": { + "Statement": [ + { + "Action": [ + "codecommit:BatchGetRepositories", + "codecommit:Get*", + "codecommit:GitPull", + "codecommit:List*" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAJACNSXR7Z2VLJW3D6", + "PolicyName": "AWSCodeCommitReadOnly", + "UpdateDate": "2015-07-09T17:05:06+00:00", + "VersionId": "v1" + }, + "AWSCodeDeployDeployerAccess": { + "Arn": "arn:aws:iam::aws:policy/AWSCodeDeployDeployerAccess", + "AttachmentCount": 0, + "CreateDate": "2015-05-19T18:18:43+00:00", + "DefaultVersionId": "v1", + "Document": { + "Statement": [ + { + "Action": [ + "codedeploy:Batch*", + "codedeploy:CreateDeployment", + "codedeploy:Get*", + "codedeploy:List*", + "codedeploy:RegisterApplicationRevision" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAJUWEPOMGLMVXJAPUI", + "PolicyName": "AWSCodeDeployDeployerAccess", + "UpdateDate": "2015-05-19T18:18:43+00:00", + "VersionId": "v1" + }, + "AWSCodeDeployFullAccess": { + "Arn": "arn:aws:iam::aws:policy/AWSCodeDeployFullAccess", + "AttachmentCount": 0, + "CreateDate": "2015-05-19T18:13:23+00:00", + "DefaultVersionId": "v1", + "Document": { + "Statement": [ + { + "Action": "codedeploy:*", + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAIONKN3TJZUKXCHXWC", + "PolicyName": "AWSCodeDeployFullAccess", + "UpdateDate": "2015-05-19T18:13:23+00:00", + "VersionId": "v1" + }, + "AWSCodeDeployReadOnlyAccess": { + "Arn": "arn:aws:iam::aws:policy/AWSCodeDeployReadOnlyAccess", + "AttachmentCount": 0, + "CreateDate": "2015-05-19T18:21:32+00:00", + "DefaultVersionId": "v1", + "Document": { + "Statement": [ + { + "Action": [ + "codedeploy:Batch*", + "codedeploy:Get*", + "codedeploy:List*" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAILZHHKCKB4NE7XOIQ", + "PolicyName": "AWSCodeDeployReadOnlyAccess", + "UpdateDate": "2015-05-19T18:21:32+00:00", + "VersionId": "v1" + }, + "AWSCodeDeployRole": { + "Arn": "arn:aws:iam::aws:policy/service-role/AWSCodeDeployRole", + "AttachmentCount": 0, + "CreateDate": "2017-09-11T19:09:51+00:00", + "DefaultVersionId": "v6", + "Document": { + "Statement": [ + { + "Action": [ + "autoscaling:CompleteLifecycleAction", + "autoscaling:DeleteLifecycleHook", + "autoscaling:DescribeAutoScalingGroups", + "autoscaling:DescribeLifecycleHooks", + "autoscaling:PutLifecycleHook", + "autoscaling:RecordLifecycleActionHeartbeat", + "autoscaling:CreateAutoScalingGroup", + "autoscaling:UpdateAutoScalingGroup", + "autoscaling:EnableMetricsCollection", + "autoscaling:DescribeAutoScalingGroups", + "autoscaling:DescribePolicies", + "autoscaling:DescribeScheduledActions", + "autoscaling:DescribeNotificationConfigurations", + "autoscaling:DescribeLifecycleHooks", + "autoscaling:SuspendProcesses", + "autoscaling:ResumeProcesses", + "autoscaling:AttachLoadBalancers", + "autoscaling:PutScalingPolicy", + "autoscaling:PutScheduledUpdateGroupAction", + "autoscaling:PutNotificationConfiguration", + "autoscaling:PutLifecycleHook", + "autoscaling:DescribeScalingActivities", + "autoscaling:DeleteAutoScalingGroup", + "ec2:DescribeInstances", + "ec2:DescribeInstanceStatus", + "ec2:TerminateInstances", + "tag:GetTags", + "tag:GetResources", + "sns:Publish", + "cloudwatch:DescribeAlarms", + "cloudwatch:PutMetricAlarm", + "elasticloadbalancing:DescribeLoadBalancers", + "elasticloadbalancing:DescribeInstanceHealth", + "elasticloadbalancing:RegisterInstancesWithLoadBalancer", + "elasticloadbalancing:DeregisterInstancesFromLoadBalancer", + "elasticloadbalancing:DescribeTargetGroups", + "elasticloadbalancing:DescribeTargetHealth", + "elasticloadbalancing:RegisterTargets", + "elasticloadbalancing:DeregisterTargets" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/service-role/", + "PolicyId": "ANPAJ2NKMKD73QS5NBFLA", + "PolicyName": "AWSCodeDeployRole", + "UpdateDate": "2017-09-11T19:09:51+00:00", + "VersionId": "v6" + }, + "AWSCodePipelineApproverAccess": { + "Arn": "arn:aws:iam::aws:policy/AWSCodePipelineApproverAccess", + "AttachmentCount": 0, + "CreateDate": "2017-08-02T17:24:58+00:00", + "DefaultVersionId": "v3", + "Document": { + "Statement": [ + { + "Action": [ + "codepipeline:GetPipeline", + "codepipeline:GetPipelineState", + "codepipeline:GetPipelineExecution", + "codepipeline:ListPipelineExecutions", + "codepipeline:ListPipelines", + "codepipeline:PutApprovalResult" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAICXNWK42SQ6LMDXM2", + "PolicyName": "AWSCodePipelineApproverAccess", + "UpdateDate": "2017-08-02T17:24:58+00:00", + "VersionId": "v3" + }, + "AWSCodePipelineCustomActionAccess": { + "Arn": "arn:aws:iam::aws:policy/AWSCodePipelineCustomActionAccess", + "AttachmentCount": 0, + "CreateDate": "2015-07-09T17:02:54+00:00", + "DefaultVersionId": "v1", + "Document": { + "Statement": [ + { + "Action": [ + "codepipeline:AcknowledgeJob", + "codepipeline:GetJobDetails", + "codepipeline:PollForJobs", + "codepipeline:PutJobFailureResult", + "codepipeline:PutJobSuccessResult" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAJFW5Z32BTVF76VCYC", + "PolicyName": "AWSCodePipelineCustomActionAccess", + "UpdateDate": "2015-07-09T17:02:54+00:00", + "VersionId": "v1" + }, + "AWSCodePipelineFullAccess": { + "Arn": "arn:aws:iam::aws:policy/AWSCodePipelineFullAccess", + "AttachmentCount": 0, + "CreateDate": "2016-11-01T19:59:46+00:00", + "DefaultVersionId": "v5", + "Document": { + "Statement": [ + { + "Action": [ + "codepipeline:*", + "iam:ListRoles", + "iam:PassRole", + "s3:CreateBucket", + "s3:GetBucketPolicy", + "s3:GetObject", + "s3:ListAllMyBuckets", + "s3:ListBucket", + "s3:PutBucketPolicy", + "codecommit:ListBranches", + "codecommit:ListRepositories", + "codedeploy:GetApplication", + "codedeploy:GetDeploymentGroup", + "codedeploy:ListApplications", + "codedeploy:ListDeploymentGroups", + "elasticbeanstalk:DescribeApplications", + "elasticbeanstalk:DescribeEnvironments", + "lambda:GetFunctionConfiguration", + "lambda:ListFunctions", + "opsworks:DescribeApps", + "opsworks:DescribeLayers", + "opsworks:DescribeStacks", + "cloudformation:DescribeStacks", + "cloudformation:ListChangeSets" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAJP5LH77KSAT2KHQGG", + "PolicyName": "AWSCodePipelineFullAccess", + "UpdateDate": "2016-11-01T19:59:46+00:00", + "VersionId": "v5" + }, + "AWSCodePipelineReadOnlyAccess": { + "Arn": "arn:aws:iam::aws:policy/AWSCodePipelineReadOnlyAccess", + "AttachmentCount": 0, + "CreateDate": "2017-08-02T17:25:18+00:00", + "DefaultVersionId": "v6", + "Document": { + "Statement": [ + { + "Action": [ + "codepipeline:GetPipeline", + "codepipeline:GetPipelineState", + "codepipeline:GetPipelineExecution", + "codepipeline:ListPipelineExecutions", + "codepipeline:ListActionTypes", + "codepipeline:ListPipelines", + "iam:ListRoles", + "s3:GetBucketPolicy", + "s3:GetObject", + "s3:ListAllMyBuckets", + "s3:ListBucket", + "codecommit:ListBranches", + "codecommit:ListRepositories", + "codedeploy:GetApplication", + "codedeploy:GetDeploymentGroup", + "codedeploy:ListApplications", + "codedeploy:ListDeploymentGroups", + "elasticbeanstalk:DescribeApplications", + "elasticbeanstalk:DescribeEnvironments", + "lambda:GetFunctionConfiguration", + "lambda:ListFunctions", + "opsworks:DescribeApps", + "opsworks:DescribeLayers", + "opsworks:DescribeStacks" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAILFKZXIBOTNC5TO2Q", + "PolicyName": "AWSCodePipelineReadOnlyAccess", + "UpdateDate": "2017-08-02T17:25:18+00:00", + "VersionId": "v6" + }, + "AWSCodeStarFullAccess": { + "Arn": "arn:aws:iam::aws:policy/AWSCodeStarFullAccess", + "AttachmentCount": 0, + "CreateDate": "2017-04-19T16:23:19+00:00", + "DefaultVersionId": "v1", + "Document": { + "Statement": [ + { + "Action": [ + "codestar:*", + "ec2:DescribeKeyPairs", + "ec2:DescribeVpcs", + "ec2:DescribeSubnets" + ], + "Effect": "Allow", + "Resource": "*", + "Sid": "CodeStarEC2" + }, + { + "Action": [ + "cloudformation:DescribeStack*", + "cloudformation:GetTemplateSummary" + ], + "Effect": "Allow", + "Resource": [ + "arn:aws:cloudformation:*:*:stack/awscodestar-*" + ], + "Sid": "CodeStarCF" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAIXI233TFUGLZOJBEC", + "PolicyName": "AWSCodeStarFullAccess", + "UpdateDate": "2017-04-19T16:23:19+00:00", + "VersionId": "v1" + }, + "AWSCodeStarServiceRole": { + "Arn": "arn:aws:iam::aws:policy/service-role/AWSCodeStarServiceRole", + "AttachmentCount": 0, + "CreateDate": "2017-07-13T19:53:22+00:00", + "DefaultVersionId": "v2", + "Document": { + "Statement": [ + { + "Action": [ + "cloudformation:*Stack*", + "cloudformation:GetTemplate" + ], + "Effect": "Allow", + "Resource": [ + "arn:aws:cloudformation:*:*:stack/awscodestar-*", + "arn:aws:cloudformation:*:*:stack/awseb-*" + ], + "Sid": "ProjectStack" + }, + { + "Action": [ + "cloudformation:GetTemplateSummary", + "cloudformation:DescribeChangeSet" + ], + "Effect": "Allow", + "Resource": "*", + "Sid": "ProjectStackTemplate" + }, + { + "Action": [ + "s3:GetObject" + ], + "Effect": "Allow", + "Resource": [ + "arn:aws:s3:::awscodestar-*/*" + ], + "Sid": "ProjectQuickstarts" + }, + { + "Action": [ + "s3:*" + ], + "Effect": "Allow", + "Resource": [ + "arn:aws:s3:::aws-codestar-*", + "arn:aws:s3:::aws-codestar-*/*", + "arn:aws:s3:::elasticbeanstalk-*", + "arn:aws:s3:::elasticbeanstalk-*/*" + ], + "Sid": "ProjectS3Buckets" + }, + { + "Action": [ + "codestar:*Project", + "codestar:*Resource*", + "codestar:List*", + "codestar:Describe*", + "codestar:Get*", + "codestar:AssociateTeamMember", + "codecommit:*", + "codepipeline:*", + "codedeploy:*", + "codebuild:*", + "ec2:RunInstances", + "autoscaling:*", + "cloudwatch:Put*", + "ec2:*", + "elasticbeanstalk:*", + "elasticloadbalancing:*", + "iam:ListRoles", + "logs:*", + "sns:*" + ], + "Effect": "Allow", + "Resource": "*", + "Sid": "ProjectServices" + }, + { + "Action": [ + "iam:AttachRolePolicy", + "iam:CreateRole", + "iam:DeleteRole", + "iam:DeleteRolePolicy", + "iam:DetachRolePolicy", + "iam:GetRole", + "iam:PassRole", + "iam:PutRolePolicy", + "iam:SetDefaultPolicyVersion", + "iam:CreatePolicy", + "iam:DeletePolicy", + "iam:AddRoleToInstanceProfile", + "iam:CreateInstanceProfile", + "iam:DeleteInstanceProfile", + "iam:RemoveRoleFromInstanceProfile" + ], + "Effect": "Allow", + "Resource": [ + "arn:aws:iam::*:role/CodeStarWorker*", + "arn:aws:iam::*:policy/CodeStarWorker*", + "arn:aws:iam::*:instance-profile/awscodestar-*" + ], + "Sid": "ProjectWorkerRoles" + }, + { + "Action": [ + "iam:AttachUserPolicy", + "iam:DetachUserPolicy" + ], + "Condition": { + "ArnEquals": { + "iam:PolicyArn": [ + "arn:aws:iam::*:policy/CodeStar_*" + ] + } + }, + "Effect": "Allow", + "Resource": "*", + "Sid": "ProjectTeamMembers" + }, + { + "Action": [ + "iam:CreatePolicy", + "iam:DeletePolicy", + "iam:CreatePolicyVersion", + "iam:DeletePolicyVersion", + "iam:ListEntitiesForPolicy", + "iam:ListPolicyVersions" + ], + "Effect": "Allow", + "Resource": [ + "arn:aws:iam::*:policy/CodeStar_*" + ], + "Sid": "ProjectRoles" + }, + { + "Action": [ + "iam:ListAttachedRolePolicies" + ], + "Effect": "Allow", + "Resource": [ + "arn:aws:iam::*:role/aws-codestar-service-role", + "arn:aws:iam::*:role/service-role/aws-codestar-service-role" + ], + "Sid": "InspectServiceRole" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/service-role/", + "PolicyId": "ANPAIN6D4M2KD3NBOC4M4", + "PolicyName": "AWSCodeStarServiceRole", + "UpdateDate": "2017-07-13T19:53:22+00:00", + "VersionId": "v2" + }, + "AWSConfigRole": { + "Arn": "arn:aws:iam::aws:policy/service-role/AWSConfigRole", + "AttachmentCount": 0, + "CreateDate": "2017-08-14T19:04:46+00:00", + "DefaultVersionId": "v10", + "Document": { + "Statement": [ + { + "Action": [ + "cloudtrail:DescribeTrails", + "ec2:Describe*", + "config:Put*", + "config:Get*", + "config:List*", + "config:Describe*", + "cloudtrail:GetTrailStatus", + "s3:GetObject", + "iam:GetAccountAuthorizationDetails", + "iam:GetAccountPasswordPolicy", + "iam:GetAccountSummary", + "iam:GetGroup", + "iam:GetGroupPolicy", + "iam:GetPolicy", + "iam:GetPolicyVersion", + "iam:GetRole", + "iam:GetRolePolicy", + "iam:GetUser", + "iam:GetUserPolicy", + "iam:ListAttachedGroupPolicies", + "iam:ListAttachedRolePolicies", + "iam:ListAttachedUserPolicies", + "iam:ListEntitiesForPolicy", + "iam:ListGroupPolicies", + "iam:ListGroupsForUser", + "iam:ListInstanceProfilesForRole", + "iam:ListPolicyVersions", + "iam:ListRolePolicies", + "iam:ListUserPolicies", + "elasticloadbalancing:DescribeLoadBalancers", + "elasticloadbalancing:DescribeLoadBalancerAttributes", + "elasticloadbalancing:DescribeLoadBalancerPolicies", + "elasticloadbalancing:DescribeTags", + "acm:DescribeCertificate", + "acm:ListCertificates", + "acm:ListTagsForCertificate", + "rds:DescribeDBInstances", + "rds:DescribeDBSecurityGroups", + "rds:DescribeDBSnapshotAttributes", + "rds:DescribeDBSnapshots", + "rds:DescribeDBSubnetGroups", + "rds:DescribeEventSubscriptions", + "rds:ListTagsForResource", + "rds:DescribeDBClusters", + "s3:GetAccelerateConfiguration", + "s3:GetBucketAcl", + "s3:GetBucketCORS", + "s3:GetBucketLocation", + "s3:GetBucketLogging", + "s3:GetBucketNotification", + "s3:GetBucketPolicy", + "s3:GetBucketRequestPayment", + "s3:GetBucketTagging", + "s3:GetBucketVersioning", + "s3:GetBucketWebsite", + "s3:GetLifecycleConfiguration", + "s3:GetReplicationConfiguration", + "s3:ListAllMyBuckets", + "redshift:DescribeClusterParameterGroups", + "redshift:DescribeClusterParameters", + "redshift:DescribeClusterSecurityGroups", + "redshift:DescribeClusterSnapshots", + "redshift:DescribeClusterSubnetGroups", + "redshift:DescribeClusters", + "redshift:DescribeEventSubscriptions", + "redshift:DescribeLoggingStatus", + "dynamodb:DescribeLimits", + "dynamodb:DescribeTable", + "dynamodb:ListTables", + "dynamodb:ListTagsOfResource", + "cloudwatch:DescribeAlarms", + "application-autoscaling:DescribeScalableTargets", + "application-autoscaling:DescribeScalingPolicies", + "autoscaling:DescribeAutoScalingGroups", + "autoscaling:DescribeLaunchConfigurations", + "autoscaling:DescribeLifecycleHooks", + "autoscaling:DescribePolicies", + "autoscaling:DescribeScheduledActions", + "autoscaling:DescribeTags" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/service-role/", + "PolicyId": "ANPAIQRXRDRGJUA33ELIO", + "PolicyName": "AWSConfigRole", + "UpdateDate": "2017-08-14T19:04:46+00:00", + "VersionId": "v10" + }, + "AWSConfigRulesExecutionRole": { + "Arn": "arn:aws:iam::aws:policy/service-role/AWSConfigRulesExecutionRole", + "AttachmentCount": 0, + "CreateDate": "2016-03-25T17:59:36+00:00", + "DefaultVersionId": "v1", + "Document": { + "Statement": [ + { + "Action": [ + "s3:GetObject" + ], + "Effect": "Allow", + "Resource": "arn:aws:s3:::*/AWSLogs/*/Config/*" + }, + { + "Action": [ + "config:Put*", + "config:Get*", + "config:List*", + "config:Describe*" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/service-role/", + "PolicyId": "ANPAJUB3KIKTA4PU4OYAA", + "PolicyName": "AWSConfigRulesExecutionRole", + "UpdateDate": "2016-03-25T17:59:36+00:00", + "VersionId": "v1" + }, + "AWSConfigUserAccess": { + "Arn": "arn:aws:iam::aws:policy/AWSConfigUserAccess", + "AttachmentCount": 0, + "CreateDate": "2016-08-30T19:15:19+00:00", + "DefaultVersionId": "v3", + "Document": { + "Statement": [ + { + "Action": [ + "config:Get*", + "config:Describe*", + "config:Deliver*", + "config:List*", + "tag:GetResources", + "tag:GetTagKeys", + "cloudtrail:DescribeTrails", + "cloudtrail:GetTrailStatus", + "cloudtrail:LookupEvents" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAIWTTSFJ7KKJE3MWGA", + "PolicyName": "AWSConfigUserAccess", + "UpdateDate": "2016-08-30T19:15:19+00:00", + "VersionId": "v3" + }, + "AWSConnector": { + "Arn": "arn:aws:iam::aws:policy/AWSConnector", + "AttachmentCount": 0, + "CreateDate": "2015-09-28T19:50:38+00:00", + "DefaultVersionId": "v3", + "Document": { + "Statement": [ + { + "Action": "iam:GetUser", + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": [ + "s3:ListAllMyBuckets" + ], + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": [ + "s3:CreateBucket", + "s3:DeleteBucket", + "s3:DeleteObject", + "s3:GetBucketLocation", + "s3:GetObject", + "s3:ListBucket", + "s3:PutObject", + "s3:PutObjectAcl", + "s3:AbortMultipartUpload", + "s3:ListBucketMultipartUploads", + "s3:ListMultipartUploadParts" + ], + "Effect": "Allow", + "Resource": "arn:aws:s3:::import-to-ec2-*" + }, + { + "Action": [ + "ec2:CancelConversionTask", + "ec2:CancelExportTask", + "ec2:CreateImage", + "ec2:CreateInstanceExportTask", + "ec2:CreateTags", + "ec2:CreateVolume", + "ec2:DeleteTags", + "ec2:DeleteVolume", + "ec2:DescribeConversionTasks", + "ec2:DescribeExportTasks", + "ec2:DescribeImages", + "ec2:DescribeInstanceAttribute", + "ec2:DescribeInstanceStatus", + "ec2:DescribeInstances", + "ec2:DescribeRegions", + "ec2:DescribeTags", + "ec2:DetachVolume", + "ec2:ImportInstance", + "ec2:ImportVolume", + "ec2:ModifyInstanceAttribute", + "ec2:RunInstances", + "ec2:StartInstances", + "ec2:StopInstances", + "ec2:TerminateInstances", + "ec2:ImportImage", + "ec2:DescribeImportImageTasks", + "ec2:DeregisterImage", + "ec2:DescribeSnapshots", + "ec2:DeleteSnapshot", + "ec2:CancelImportTask", + "ec2:ImportSnapshot", + "ec2:DescribeImportSnapshotTasks" + ], + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": [ + "SNS:Publish" + ], + "Effect": "Allow", + "Resource": "arn:aws:sns:*:*:metrics-sns-topic-for-*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAJ6YATONJHICG3DJ3U", + "PolicyName": "AWSConnector", + "UpdateDate": "2015-09-28T19:50:38+00:00", + "VersionId": "v3" + }, + "AWSDataPipelineRole": { + "Arn": "arn:aws:iam::aws:policy/service-role/AWSDataPipelineRole", + "AttachmentCount": 0, + "CreateDate": "2016-02-22T17:17:38+00:00", + "DefaultVersionId": "v5", + "Document": { + "Statement": [ + { + "Action": [ + "cloudwatch:*", + "datapipeline:DescribeObjects", + "datapipeline:EvaluateExpression", + "dynamodb:BatchGetItem", + "dynamodb:DescribeTable", + "dynamodb:GetItem", + "dynamodb:Query", + "dynamodb:Scan", + "dynamodb:UpdateTable", + "ec2:AuthorizeSecurityGroupIngress", + "ec2:CancelSpotInstanceRequests", + "ec2:CreateSecurityGroup", + "ec2:CreateTags", + "ec2:DeleteTags", + "ec2:Describe*", + "ec2:ModifyImageAttribute", + "ec2:ModifyInstanceAttribute", + "ec2:RequestSpotInstances", + "ec2:RunInstances", + "ec2:StartInstances", + "ec2:StopInstances", + "ec2:TerminateInstances", + "ec2:AuthorizeSecurityGroupEgress", + "ec2:DeleteSecurityGroup", + "ec2:RevokeSecurityGroupEgress", + "ec2:DescribeNetworkInterfaces", + "ec2:CreateNetworkInterface", + "ec2:DeleteNetworkInterface", + "ec2:DetachNetworkInterface", + "elasticmapreduce:*", + "iam:GetInstanceProfile", + "iam:GetRole", + "iam:GetRolePolicy", + "iam:ListAttachedRolePolicies", + "iam:ListRolePolicies", + "iam:ListInstanceProfiles", + "iam:PassRole", + "rds:DescribeDBInstances", + "rds:DescribeDBSecurityGroups", + "redshift:DescribeClusters", + "redshift:DescribeClusterSecurityGroups", + "s3:CreateBucket", + "s3:DeleteObject", + "s3:Get*", + "s3:List*", + "s3:Put*", + "sdb:BatchPutAttributes", + "sdb:Select*", + "sns:GetTopicAttributes", + "sns:ListTopics", + "sns:Publish", + "sns:Subscribe", + "sns:Unsubscribe", + "sqs:CreateQueue", + "sqs:Delete*", + "sqs:GetQueue*", + "sqs:PurgeQueue", + "sqs:ReceiveMessage" + ], + "Effect": "Allow", + "Resource": [ + "*" + ] + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/service-role/", + "PolicyId": "ANPAIKCP6XS3ESGF4GLO2", + "PolicyName": "AWSDataPipelineRole", + "UpdateDate": "2016-02-22T17:17:38+00:00", + "VersionId": "v5" + }, + "AWSDataPipeline_FullAccess": { + "Arn": "arn:aws:iam::aws:policy/AWSDataPipeline_FullAccess", + "AttachmentCount": 0, + "CreateDate": "2017-08-17T18:48:39+00:00", + "DefaultVersionId": "v2", + "Document": { + "Statement": [ + { + "Action": [ + "s3:List*", + "dynamodb:DescribeTable", + "rds:DescribeDBInstances", + "rds:DescribeDBSecurityGroups", + "redshift:DescribeClusters", + "redshift:DescribeClusterSecurityGroups", + "sns:ListTopics", + "sns:Subscribe", + "iam:ListRoles", + "iam:GetRolePolicy", + "iam:GetInstanceProfile", + "iam:ListInstanceProfiles", + "datapipeline:*" + ], + "Effect": "Allow", + "Resource": [ + "*" + ] + }, + { + "Action": "iam:PassRole", + "Effect": "Allow", + "Resource": [ + "arn:aws:iam::*:role/DataPipelineDefaultResourceRole", + "arn:aws:iam::*:role/DataPipelineDefaultRole" + ] + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAIXOFIG7RSBMRPHXJ4", + "PolicyName": "AWSDataPipeline_FullAccess", + "UpdateDate": "2017-08-17T18:48:39+00:00", + "VersionId": "v2" + }, + "AWSDataPipeline_PowerUser": { + "Arn": "arn:aws:iam::aws:policy/AWSDataPipeline_PowerUser", + "AttachmentCount": 0, + "CreateDate": "2017-08-17T18:49:42+00:00", + "DefaultVersionId": "v2", + "Document": { + "Statement": [ + { + "Action": [ + "s3:List*", + "dynamodb:DescribeTable", + "rds:DescribeDBInstances", + "rds:DescribeDBSecurityGroups", + "redshift:DescribeClusters", + "redshift:DescribeClusterSecurityGroups", + "sns:ListTopics", + "iam:ListRoles", + "iam:GetRolePolicy", + "iam:GetInstanceProfile", + "iam:ListInstanceProfiles", + "datapipeline:*" + ], + "Effect": "Allow", + "Resource": [ + "*" + ] + }, + { + "Action": "iam:PassRole", + "Effect": "Allow", + "Resource": [ + "arn:aws:iam::*:role/DataPipelineDefaultResourceRole", + "arn:aws:iam::*:role/DataPipelineDefaultRole" + ] + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAIMXGLVY6DVR24VTYS", + "PolicyName": "AWSDataPipeline_PowerUser", + "UpdateDate": "2017-08-17T18:49:42+00:00", + "VersionId": "v2" + }, + "AWSDeviceFarmFullAccess": { + "Arn": "arn:aws:iam::aws:policy/AWSDeviceFarmFullAccess", + "AttachmentCount": 0, + "CreateDate": "2015-07-13T16:37:38+00:00", + "DefaultVersionId": "v1", + "Document": { + "Statement": [ + { + "Action": [ + "devicefarm:*" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAJO7KEDP4VYJPNT5UW", + "PolicyName": "AWSDeviceFarmFullAccess", + "UpdateDate": "2015-07-13T16:37:38+00:00", + "VersionId": "v1" + }, + "AWSDirectConnectFullAccess": { + "Arn": "arn:aws:iam::aws:policy/AWSDirectConnectFullAccess", + "AttachmentCount": 0, + "CreateDate": "2015-02-06T18:40:07+00:00", + "DefaultVersionId": "v1", + "Document": { + "Statement": [ + { + "Action": [ + "directconnect:*" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAJQF2QKZSK74KTIHOW", + "PolicyName": "AWSDirectConnectFullAccess", + "UpdateDate": "2015-02-06T18:40:07+00:00", + "VersionId": "v1" + }, + "AWSDirectConnectReadOnlyAccess": { + "Arn": "arn:aws:iam::aws:policy/AWSDirectConnectReadOnlyAccess", + "AttachmentCount": 0, + "CreateDate": "2015-02-06T18:40:08+00:00", + "DefaultVersionId": "v1", + "Document": { + "Statement": [ + { + "Action": [ + "directconnect:Describe*" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAI23HZ27SI6FQMGNQ2", + "PolicyName": "AWSDirectConnectReadOnlyAccess", + "UpdateDate": "2015-02-06T18:40:08+00:00", + "VersionId": "v1" + }, + "AWSDirectoryServiceFullAccess": { + "Arn": "arn:aws:iam::aws:policy/AWSDirectoryServiceFullAccess", + "AttachmentCount": 0, + "CreateDate": "2016-02-24T23:10:36+00:00", + "DefaultVersionId": "v2", + "Document": { + "Statement": [ + { + "Action": [ + "ds:*", + "ec2:AuthorizeSecurityGroupEgress", + "ec2:AuthorizeSecurityGroupIngress", + "ec2:CreateNetworkInterface", + "ec2:CreateSecurityGroup", + "ec2:DeleteNetworkInterface", + "ec2:DeleteSecurityGroup", + "ec2:DescribeNetworkInterfaces", + "ec2:DescribeSubnets", + "ec2:DescribeVpcs", + "ec2:RevokeSecurityGroupEgress", + "ec2:RevokeSecurityGroupIngress", + "sns:GetTopicAttributes", + "sns:ListSubscriptions", + "sns:ListSubscriptionsByTopic", + "sns:ListTopics" + ], + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": [ + "sns:CreateTopic", + "sns:DeleteTopic", + "sns:SetTopicAttributes", + "sns:Subscribe", + "sns:Unsubscribe" + ], + "Effect": "Allow", + "Resource": "arn:aws:sns:*:*:DirectoryMonitoring*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAINAW5ANUWTH3R4ANI", + "PolicyName": "AWSDirectoryServiceFullAccess", + "UpdateDate": "2016-02-24T23:10:36+00:00", + "VersionId": "v2" + }, + "AWSDirectoryServiceReadOnlyAccess": { + "Arn": "arn:aws:iam::aws:policy/AWSDirectoryServiceReadOnlyAccess", + "AttachmentCount": 0, + "CreateDate": "2016-02-24T23:11:18+00:00", + "DefaultVersionId": "v3", + "Document": { + "Statement": [ + { + "Action": [ + "ds:Check*", + "ds:Describe*", + "ds:Get*", + "ds:List*", + "ds:Verify*", + "ec2:DescribeNetworkInterfaces", + "ec2:DescribeSubnets", + "ec2:DescribeVpcs", + "sns:ListTopics", + "sns:GetTopicAttributes", + "sns:ListSubscriptions", + "sns:ListSubscriptionsByTopic" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAIHWYO6WSDNCG64M2W", + "PolicyName": "AWSDirectoryServiceReadOnlyAccess", + "UpdateDate": "2016-02-24T23:11:18+00:00", + "VersionId": "v3" + }, + "AWSEC2SpotServiceRolePolicy": { + "Arn": "arn:aws:iam::aws:policy/aws-service-role/AWSEC2SpotServiceRolePolicy", + "AttachmentCount": 0, + "CreateDate": "2017-09-18T18:51:54+00:00", + "DefaultVersionId": "v1", + "Document": { + "Statement": [ + { + "Action": [ + "ec2:DescribeInstances", + "ec2:StartInstances", + "ec2:StopInstances" + ], + "Effect": "Allow", + "Resource": [ + "*" + ] + }, + { + "Action": [ + "iam:PassRole" + ], + "Condition": { + "StringLike": { + "iam:PassedToService": "ec2.amazonaws.com" + } + }, + "Effect": "Allow", + "Resource": [ + "*" + ] + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/aws-service-role/", + "PolicyId": "ANPAIZJJBQNXQYVKTEXGM", + "PolicyName": "AWSEC2SpotServiceRolePolicy", + "UpdateDate": "2017-09-18T18:51:54+00:00", + "VersionId": "v1" + }, + "AWSElasticBeanstalkCustomPlatformforEC2Role": { + "Arn": "arn:aws:iam::aws:policy/AWSElasticBeanstalkCustomPlatformforEC2Role", + "AttachmentCount": 0, + "CreateDate": "2017-02-21T22:50:30+00:00", + "DefaultVersionId": "v1", + "Document": { + "Statement": [ + { + "Action": [ + "ec2:AttachVolume", + "ec2:AuthorizeSecurityGroupIngress", + "ec2:CopyImage", + "ec2:CreateImage", + "ec2:CreateKeypair", + "ec2:CreateSecurityGroup", + "ec2:CreateSnapshot", + "ec2:CreateTags", + "ec2:CreateVolume", + "ec2:DeleteKeypair", + "ec2:DeleteSecurityGroup", + "ec2:DeleteSnapshot", + "ec2:DeleteVolume", + "ec2:DeregisterImage", + "ec2:DescribeImageAttribute", + "ec2:DescribeImages", + "ec2:DescribeInstances", + "ec2:DescribeRegions", + "ec2:DescribeSecurityGroups", + "ec2:DescribeSnapshots", + "ec2:DescribeSubnets", + "ec2:DescribeTags", + "ec2:DescribeVolumes", + "ec2:DetachVolume", + "ec2:GetPasswordData", + "ec2:ModifyImageAttribute", + "ec2:ModifyInstanceAttribute", + "ec2:ModifySnapshotAttribute", + "ec2:RegisterImage", + "ec2:RunInstances", + "ec2:StopInstances", + "ec2:TerminateInstances" + ], + "Effect": "Allow", + "Resource": "*", + "Sid": "EC2Access" + }, + { + "Action": [ + "s3:Get*", + "s3:List*", + "s3:PutObject" + ], + "Effect": "Allow", + "Resource": [ + "arn:aws:s3:::elasticbeanstalk-*", + "arn:aws:s3:::elasticbeanstalk-*/*" + ], + "Sid": "BucketAccess" + }, + { + "Action": [ + "logs:CreateLogGroup", + "logs:CreateLogStream", + "logs:PutLogEvents", + "logs:DescribeLogStreams" + ], + "Effect": "Allow", + "Resource": "arn:aws:logs:*:*:log-group:/aws/elasticbeanstalk/platform/*", + "Sid": "CloudWatchLogsAccess" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAJRVFXSS6LEIQGBKDY", + "PolicyName": "AWSElasticBeanstalkCustomPlatformforEC2Role", + "UpdateDate": "2017-02-21T22:50:30+00:00", + "VersionId": "v1" + }, + "AWSElasticBeanstalkEnhancedHealth": { + "Arn": "arn:aws:iam::aws:policy/service-role/AWSElasticBeanstalkEnhancedHealth", + "AttachmentCount": 0, + "CreateDate": "2016-08-22T20:28:36+00:00", + "DefaultVersionId": "v2", + "Document": { + "Statement": [ + { + "Action": [ + "elasticloadbalancing:DescribeInstanceHealth", + "elasticloadbalancing:DescribeLoadBalancers", + "elasticloadbalancing:DescribeTargetHealth", + "ec2:DescribeInstances", + "ec2:DescribeInstanceStatus", + "ec2:GetConsoleOutput", + "ec2:AssociateAddress", + "ec2:DescribeAddresses", + "ec2:DescribeSecurityGroups", + "sqs:GetQueueAttributes", + "sqs:GetQueueUrl", + "autoscaling:DescribeAutoScalingGroups", + "autoscaling:DescribeAutoScalingInstances", + "autoscaling:DescribeScalingActivities", + "autoscaling:DescribeNotificationConfigurations" + ], + "Effect": "Allow", + "Resource": [ + "*" + ] + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/service-role/", + "PolicyId": "ANPAIH5EFJNMOGUUTKLFE", + "PolicyName": "AWSElasticBeanstalkEnhancedHealth", + "UpdateDate": "2016-08-22T20:28:36+00:00", + "VersionId": "v2" + }, + "AWSElasticBeanstalkFullAccess": { + "Arn": "arn:aws:iam::aws:policy/AWSElasticBeanstalkFullAccess", + "AttachmentCount": 0, + "CreateDate": "2016-12-21T01:00:13+00:00", + "DefaultVersionId": "v5", + "Document": { + "Statement": [ + { + "Action": [ + "elasticbeanstalk:*", + "ec2:*", + "ecs:*", + "ecr:*", + "elasticloadbalancing:*", + "autoscaling:*", + "cloudwatch:*", + "s3:*", + "sns:*", + "cloudformation:*", + "dynamodb:*", + "rds:*", + "sqs:*", + "logs:*", + "iam:GetPolicyVersion", + "iam:GetRole", + "iam:PassRole", + "iam:ListRolePolicies", + "iam:ListAttachedRolePolicies", + "iam:ListInstanceProfiles", + "iam:ListRoles", + "iam:ListServerCertificates", + "acm:DescribeCertificate", + "acm:ListCertificates", + "codebuild:CreateProject", + "codebuild:DeleteProject", + "codebuild:BatchGetBuilds", + "codebuild:StartBuild" + ], + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": [ + "iam:AddRoleToInstanceProfile", + "iam:CreateInstanceProfile", + "iam:CreateRole" + ], + "Effect": "Allow", + "Resource": [ + "arn:aws:iam::*:role/aws-elasticbeanstalk*", + "arn:aws:iam::*:instance-profile/aws-elasticbeanstalk*" + ] + }, + { + "Action": [ + "iam:AttachRolePolicy" + ], + "Condition": { + "StringLike": { + "iam:PolicyArn": [ + "arn:aws:iam::aws:policy/AWSElasticBeanstalk*", + "arn:aws:iam::aws:policy/service-role/AWSElasticBeanstalk*" + ] + } + }, + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAIZYX2YLLBW2LJVUFW", + "PolicyName": "AWSElasticBeanstalkFullAccess", + "UpdateDate": "2016-12-21T01:00:13+00:00", + "VersionId": "v5" + }, + "AWSElasticBeanstalkMulticontainerDocker": { + "Arn": "arn:aws:iam::aws:policy/AWSElasticBeanstalkMulticontainerDocker", + "AttachmentCount": 0, + "CreateDate": "2016-06-06T23:45:37+00:00", + "DefaultVersionId": "v2", + "Document": { + "Statement": [ + { + "Action": [ + "ecs:Poll", + "ecs:StartTask", + "ecs:StopTask", + "ecs:DiscoverPollEndpoint", + "ecs:StartTelemetrySession", + "ecs:RegisterContainerInstance", + "ecs:DeregisterContainerInstance", + "ecs:DescribeContainerInstances", + "ecs:Submit*", + "ecs:DescribeTasks" + ], + "Effect": "Allow", + "Resource": "*", + "Sid": "ECSAccess" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAJ45SBYG72SD6SHJEY", + "PolicyName": "AWSElasticBeanstalkMulticontainerDocker", + "UpdateDate": "2016-06-06T23:45:37+00:00", + "VersionId": "v2" + }, + "AWSElasticBeanstalkReadOnlyAccess": { + "Arn": "arn:aws:iam::aws:policy/AWSElasticBeanstalkReadOnlyAccess", + "AttachmentCount": 0, + "CreateDate": "2015-02-06T18:40:19+00:00", + "DefaultVersionId": "v1", + "Document": { + "Statement": [ + { + "Action": [ + "elasticbeanstalk:Check*", + "elasticbeanstalk:Describe*", + "elasticbeanstalk:List*", + "elasticbeanstalk:RequestEnvironmentInfo", + "elasticbeanstalk:RetrieveEnvironmentInfo", + "ec2:Describe*", + "elasticloadbalancing:Describe*", + "autoscaling:Describe*", + "cloudwatch:Describe*", + "cloudwatch:List*", + "cloudwatch:Get*", + "s3:Get*", + "s3:List*", + "sns:Get*", + "sns:List*", + "cloudformation:Describe*", + "cloudformation:Get*", + "cloudformation:List*", + "cloudformation:Validate*", + "cloudformation:Estimate*", + "rds:Describe*", + "sqs:Get*", + "sqs:List*" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAI47KNGXDAXFD4SDHG", + "PolicyName": "AWSElasticBeanstalkReadOnlyAccess", + "UpdateDate": "2015-02-06T18:40:19+00:00", + "VersionId": "v1" + }, + "AWSElasticBeanstalkService": { + "Arn": "arn:aws:iam::aws:policy/service-role/AWSElasticBeanstalkService", + "AttachmentCount": 0, + "CreateDate": "2017-06-21T16:49:23+00:00", + "DefaultVersionId": "v11", + "Document": { + "Statement": [ + { + "Action": [ + "cloudformation:*" + ], + "Effect": "Allow", + "Resource": [ + "arn:aws:cloudformation:*:*:stack/awseb-*", + "arn:aws:cloudformation:*:*:stack/eb-*" + ], + "Sid": "AllowCloudformationOperationsOnElasticBeanstalkStacks" + }, + { + "Action": [ + "logs:DeleteLogGroup" + ], + "Effect": "Allow", + "Resource": [ + "arn:aws:logs:*:*:log-group:/aws/elasticbeanstalk*" + ], + "Sid": "AllowDeleteCloudwatchLogGroups" + }, + { + "Action": [ + "s3:*" + ], + "Effect": "Allow", + "Resource": [ + "arn:aws:s3:::elasticbeanstalk-*", + "arn:aws:s3:::elasticbeanstalk-*/*" + ], + "Sid": "AllowS3OperationsOnElasticBeanstalkBuckets" + }, + { + "Action": [ + "autoscaling:AttachInstances", + "autoscaling:CreateAutoScalingGroup", + "autoscaling:CreateLaunchConfiguration", + "autoscaling:DeleteLaunchConfiguration", + "autoscaling:DeleteAutoScalingGroup", + "autoscaling:DeleteScheduledAction", + "autoscaling:DescribeAccountLimits", + "autoscaling:DescribeAutoScalingGroups", + "autoscaling:DescribeAutoScalingInstances", + "autoscaling:DescribeLaunchConfigurations", + "autoscaling:DescribeLoadBalancers", + "autoscaling:DescribeNotificationConfigurations", + "autoscaling:DescribeScalingActivities", + "autoscaling:DescribeScheduledActions", + "autoscaling:DetachInstances", + "autoscaling:PutScheduledUpdateGroupAction", + "autoscaling:ResumeProcesses", + "autoscaling:SetDesiredCapacity", + "autoscaling:SuspendProcesses", + "autoscaling:TerminateInstanceInAutoScalingGroup", + "autoscaling:UpdateAutoScalingGroup", + "cloudwatch:PutMetricAlarm", + "ec2:AssociateAddress", + "ec2:AllocateAddress", + "ec2:AuthorizeSecurityGroupEgress", + "ec2:AuthorizeSecurityGroupIngress", + "ec2:CreateSecurityGroup", + "ec2:DeleteSecurityGroup", + "ec2:DescribeAccountAttributes", + "ec2:DescribeAddresses", + "ec2:DescribeImages", + "ec2:DescribeInstances", + "ec2:DescribeKeyPairs", + "ec2:DescribeSecurityGroups", + "ec2:DescribeSnapshots", + "ec2:DescribeSubnets", + "ec2:DescribeVpcs", + "ec2:DisassociateAddress", + "ec2:ReleaseAddress", + "ec2:RevokeSecurityGroupEgress", + "ec2:RevokeSecurityGroupIngress", + "ec2:TerminateInstances", + "ecs:CreateCluster", + "ecs:DeleteCluster", + "ecs:DescribeClusters", + "ecs:RegisterTaskDefinition", + "elasticbeanstalk:*", + "elasticloadbalancing:ApplySecurityGroupsToLoadBalancer", + "elasticloadbalancing:ConfigureHealthCheck", + "elasticloadbalancing:CreateLoadBalancer", + "elasticloadbalancing:DeleteLoadBalancer", + "elasticloadbalancing:DeregisterInstancesFromLoadBalancer", + "elasticloadbalancing:DescribeInstanceHealth", + "elasticloadbalancing:DescribeLoadBalancers", + "elasticloadbalancing:DescribeTargetHealth", + "elasticloadbalancing:RegisterInstancesWithLoadBalancer", + "elasticloadbalancing:DescribeTargetGroups", + "elasticloadbalancing:RegisterTargets", + "elasticloadbalancing:DeregisterTargets", + "iam:ListRoles", + "iam:PassRole", + "logs:CreateLogGroup", + "logs:PutRetentionPolicy", + "rds:DescribeDBEngineVersions", + "rds:DescribeDBInstances", + "rds:DescribeOrderableDBInstanceOptions", + "s3:CopyObject", + "s3:GetObject", + "s3:GetObjectAcl", + "s3:GetObjectMetadata", + "s3:ListBucket", + "s3:listBuckets", + "s3:ListObjects", + "sns:CreateTopic", + "sns:GetTopicAttributes", + "sns:ListSubscriptionsByTopic", + "sns:Subscribe", + "sns:SetTopicAttributes", + "sqs:GetQueueAttributes", + "sqs:GetQueueUrl", + "codebuild:CreateProject", + "codebuild:DeleteProject", + "codebuild:BatchGetBuilds", + "codebuild:StartBuild" + ], + "Effect": "Allow", + "Resource": [ + "*" + ], + "Sid": "AllowOperations" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/service-role/", + "PolicyId": "ANPAJKQ5SN74ZQ4WASXBM", + "PolicyName": "AWSElasticBeanstalkService", + "UpdateDate": "2017-06-21T16:49:23+00:00", + "VersionId": "v11" + }, + "AWSElasticBeanstalkServiceRolePolicy": { + "Arn": "arn:aws:iam::aws:policy/aws-service-role/AWSElasticBeanstalkServiceRolePolicy", + "AttachmentCount": 0, + "CreateDate": "2017-09-13T23:46:37+00:00", + "DefaultVersionId": "v1", + "Document": { + "Statement": [ + { + "Action": [ + "iam:PassRole" + ], + "Condition": { + "StringLikeIfExists": { + "iam:PassedToService": "elasticbeanstalk.amazonaws.com" + } + }, + "Effect": "Allow", + "Resource": "*", + "Sid": "AllowPassRoleToElasticBeanstalk" + }, + { + "Action": [ + "cloudformation:*" + ], + "Effect": "Allow", + "Resource": [ + "arn:aws:cloudformation:*:*:stack/awseb-*", + "arn:aws:cloudformation:*:*:stack/eb-*" + ], + "Sid": "AllowCloudformationOperationsOnElasticBeanstalkStacks" + }, + { + "Action": [ + "logs:DeleteLogGroup" + ], + "Effect": "Allow", + "Resource": [ + "arn:aws:logs:*:*:log-group:/aws/elasticbeanstalk*" + ], + "Sid": "AllowDeleteCloudwatchLogGroups" + }, + { + "Action": [ + "s3:*" + ], + "Effect": "Allow", + "Resource": [ + "arn:aws:s3:::elasticbeanstalk-*", + "arn:aws:s3:::elasticbeanstalk-*/*" + ], + "Sid": "AllowS3OperationsOnElasticBeanstalkBuckets" + }, + { + "Action": [ + "autoscaling:AttachInstances", + "autoscaling:CreateAutoScalingGroup", + "autoscaling:CreateLaunchConfiguration", + "autoscaling:DeleteLaunchConfiguration", + "autoscaling:DeleteAutoScalingGroup", + "autoscaling:DeleteScheduledAction", + "autoscaling:DescribeAccountLimits", + "autoscaling:DescribeAutoScalingGroups", + "autoscaling:DescribeAutoScalingInstances", + "autoscaling:DescribeLaunchConfigurations", + "autoscaling:DescribeLoadBalancers", + "autoscaling:DescribeNotificationConfigurations", + "autoscaling:DescribeScalingActivities", + "autoscaling:DescribeScheduledActions", + "autoscaling:DetachInstances", + "autoscaling:PutScheduledUpdateGroupAction", + "autoscaling:ResumeProcesses", + "autoscaling:SetDesiredCapacity", + "autoscaling:SuspendProcesses", + "autoscaling:TerminateInstanceInAutoScalingGroup", + "autoscaling:UpdateAutoScalingGroup", + "cloudwatch:PutMetricAlarm", + "ec2:AssociateAddress", + "ec2:AllocateAddress", + "ec2:AuthorizeSecurityGroupEgress", + "ec2:AuthorizeSecurityGroupIngress", + "ec2:CreateSecurityGroup", + "ec2:DeleteSecurityGroup", + "ec2:DescribeAccountAttributes", + "ec2:DescribeAddresses", + "ec2:DescribeImages", + "ec2:DescribeInstances", + "ec2:DescribeKeyPairs", + "ec2:DescribeSecurityGroups", + "ec2:DescribeSubnets", + "ec2:DescribeVpcs", + "ec2:DisassociateAddress", + "ec2:ReleaseAddress", + "ec2:RevokeSecurityGroupEgress", + "ec2:RevokeSecurityGroupIngress", + "ec2:TerminateInstances", + "ecs:CreateCluster", + "ecs:DeleteCluster", + "ecs:DescribeClusters", + "ecs:RegisterTaskDefinition", + "elasticbeanstalk:*", + "elasticloadbalancing:ApplySecurityGroupsToLoadBalancer", + "elasticloadbalancing:ConfigureHealthCheck", + "elasticloadbalancing:CreateLoadBalancer", + "elasticloadbalancing:DeleteLoadBalancer", + "elasticloadbalancing:DeregisterInstancesFromLoadBalancer", + "elasticloadbalancing:DescribeInstanceHealth", + "elasticloadbalancing:DescribeLoadBalancers", + "elasticloadbalancing:DescribeTargetHealth", + "elasticloadbalancing:RegisterInstancesWithLoadBalancer", + "elasticloadbalancing:DescribeTargetGroups", + "elasticloadbalancing:RegisterTargets", + "elasticloadbalancing:DeregisterTargets", + "iam:ListRoles", + "logs:CreateLogGroup", + "logs:PutRetentionPolicy", + "rds:DescribeDBInstances", + "rds:DescribeOrderableDBInstanceOptions", + "rds:DescribeDBEngineVersions", + "sns:ListTopics", + "sns:GetTopicAttributes", + "sns:ListSubscriptionsByTopic", + "sqs:GetQueueAttributes", + "sqs:GetQueueUrl", + "codebuild:CreateProject", + "codebuild:DeleteProject", + "codebuild:BatchGetBuilds", + "codebuild:StartBuild" + ], + "Effect": "Allow", + "Resource": [ + "*" + ], + "Sid": "AllowOperations" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/aws-service-role/", + "PolicyId": "ANPAIID62QSI3OSIPQXTM", + "PolicyName": "AWSElasticBeanstalkServiceRolePolicy", + "UpdateDate": "2017-09-13T23:46:37+00:00", + "VersionId": "v1" + }, + "AWSElasticBeanstalkWebTier": { + "Arn": "arn:aws:iam::aws:policy/AWSElasticBeanstalkWebTier", + "AttachmentCount": 0, + "CreateDate": "2016-12-21T02:06:25+00:00", + "DefaultVersionId": "v4", + "Document": { + "Statement": [ + { + "Action": [ + "s3:Get*", + "s3:List*", + "s3:PutObject" + ], + "Effect": "Allow", + "Resource": [ + "arn:aws:s3:::elasticbeanstalk-*", + "arn:aws:s3:::elasticbeanstalk-*/*" + ], + "Sid": "BucketAccess" + }, + { + "Action": [ + "xray:PutTraceSegments", + "xray:PutTelemetryRecords" + ], + "Effect": "Allow", + "Resource": "*", + "Sid": "XRayAccess" + }, + { + "Action": [ + "logs:PutLogEvents", + "logs:CreateLogStream" + ], + "Effect": "Allow", + "Resource": [ + "arn:aws:logs:*:*:log-group:/aws/elasticbeanstalk*" + ], + "Sid": "CloudWatchLogsAccess" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAIUF4325SJYOREKW3A", + "PolicyName": "AWSElasticBeanstalkWebTier", + "UpdateDate": "2016-12-21T02:06:25+00:00", + "VersionId": "v4" + }, + "AWSElasticBeanstalkWorkerTier": { + "Arn": "arn:aws:iam::aws:policy/AWSElasticBeanstalkWorkerTier", + "AttachmentCount": 0, + "CreateDate": "2016-12-21T02:01:55+00:00", + "DefaultVersionId": "v4", + "Document": { + "Statement": [ + { + "Action": [ + "cloudwatch:PutMetricData" + ], + "Effect": "Allow", + "Resource": "*", + "Sid": "MetricsAccess" + }, + { + "Action": [ + "xray:PutTraceSegments", + "xray:PutTelemetryRecords" + ], + "Effect": "Allow", + "Resource": "*", + "Sid": "XRayAccess" + }, + { + "Action": [ + "sqs:ChangeMessageVisibility", + "sqs:DeleteMessage", + "sqs:ReceiveMessage", + "sqs:SendMessage" + ], + "Effect": "Allow", + "Resource": "*", + "Sid": "QueueAccess" + }, + { + "Action": [ + "s3:Get*", + "s3:List*", + "s3:PutObject" + ], + "Effect": "Allow", + "Resource": [ + "arn:aws:s3:::elasticbeanstalk-*", + "arn:aws:s3:::elasticbeanstalk-*/*" + ], + "Sid": "BucketAccess" + }, + { + "Action": [ + "dynamodb:BatchGetItem", + "dynamodb:BatchWriteItem", + "dynamodb:DeleteItem", + "dynamodb:GetItem", + "dynamodb:PutItem", + "dynamodb:Query", + "dynamodb:Scan", + "dynamodb:UpdateItem" + ], + "Effect": "Allow", + "Resource": [ + "arn:aws:dynamodb:*:*:table/*-stack-AWSEBWorkerCronLeaderRegistry*" + ], + "Sid": "DynamoPeriodicTasks" + }, + { + "Action": [ + "logs:PutLogEvents", + "logs:CreateLogStream" + ], + "Effect": "Allow", + "Resource": [ + "arn:aws:logs:*:*:log-group:/aws/elasticbeanstalk*" + ], + "Sid": "CloudWatchLogsAccess" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAJQDLBRSJVKVF4JMSK", + "PolicyName": "AWSElasticBeanstalkWorkerTier", + "UpdateDate": "2016-12-21T02:01:55+00:00", + "VersionId": "v4" + }, + "AWSElasticLoadBalancingClassicServiceRolePolicy": { + "Arn": "arn:aws:iam::aws:policy/aws-service-role/AWSElasticLoadBalancingClassicServiceRolePolicy", + "AttachmentCount": 0, + "CreateDate": "2017-09-19T22:36:18+00:00", + "DefaultVersionId": "v1", + "Document": { + "Statement": [ + { + "Action": [ + "ec2:DescribeAddresses", + "ec2:DescribeInstances", + "ec2:DescribeSubnets", + "ec2:DescribeSecurityGroups", + "ec2:DescribeVpcs", + "ec2:DescribeInternetGateways", + "ec2:DescribeAccountAttributes", + "ec2:DescribeClassicLinkInstances", + "ec2:DescribeVpcClassicLink", + "ec2:CreateSecurityGroup", + "ec2:CreateNetworkInterface", + "ec2:DeleteNetworkInterface", + "ec2:ModifyNetworkInterface", + "ec2:ModifyNetworkInterfaceAttribute", + "ec2:AuthorizeSecurityGroupIngress", + "ec2:AssociateAddress", + "ec2:DisassociateAddress", + "ec2:AttachNetworkInterface", + "ec2:DetachNetworkInterface", + "ec2:AssignPrivateIpAddresses", + "ec2:AssignIpv6Addresses", + "ec2:UnassignIpv6Addresses" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/aws-service-role/", + "PolicyId": "ANPAIUMWW3QP7DPZPNVU4", + "PolicyName": "AWSElasticLoadBalancingClassicServiceRolePolicy", + "UpdateDate": "2017-09-19T22:36:18+00:00", + "VersionId": "v1" + }, + "AWSElasticLoadBalancingServiceRolePolicy": { + "Arn": "arn:aws:iam::aws:policy/aws-service-role/AWSElasticLoadBalancingServiceRolePolicy", + "AttachmentCount": 0, + "CreateDate": "2017-09-19T22:19:04+00:00", + "DefaultVersionId": "v1", + "Document": { + "Statement": [ + { + "Action": [ + "ec2:DescribeAddresses", + "ec2:DescribeInstances", + "ec2:DescribeSubnets", + "ec2:DescribeSecurityGroups", + "ec2:DescribeVpcs", + "ec2:DescribeInternetGateways", + "ec2:DescribeAccountAttributes", + "ec2:DescribeClassicLinkInstances", + "ec2:DescribeVpcClassicLink", + "ec2:CreateSecurityGroup", + "ec2:CreateNetworkInterface", + "ec2:DeleteNetworkInterface", + "ec2:ModifyNetworkInterface", + "ec2:ModifyNetworkInterfaceAttribute", + "ec2:AuthorizeSecurityGroupIngress", + "ec2:AssociateAddress", + "ec2:DisassociateAddress", + "ec2:AttachNetworkInterface", + "ec2:DetachNetworkInterface", + "ec2:AssignPrivateIpAddresses", + "ec2:AssignIpv6Addresses", + "ec2:UnassignIpv6Addresses" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/aws-service-role/", + "PolicyId": "ANPAIMHWGGSRHLOQUICJQ", + "PolicyName": "AWSElasticLoadBalancingServiceRolePolicy", + "UpdateDate": "2017-09-19T22:19:04+00:00", + "VersionId": "v1" + }, + "AWSEnhancedClassicNetworkingMangementPolicy": { + "Arn": "arn:aws:iam::aws:policy/aws-service-role/AWSEnhancedClassicNetworkingMangementPolicy", + "AttachmentCount": 0, + "CreateDate": "2017-09-20T17:29:09+00:00", + "DefaultVersionId": "v1", + "Document": { + "Statement": [ + { + "Action": [ + "ec2:DescribeInstances", + "ec2:DescribeSecurityGroups" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/aws-service-role/", + "PolicyId": "ANPAI7T4V2HZTS72QVO52", + "PolicyName": "AWSEnhancedClassicNetworkingMangementPolicy", + "UpdateDate": "2017-09-20T17:29:09+00:00", + "VersionId": "v1" + }, + "AWSGlueConsoleFullAccess": { + "Arn": "arn:aws:iam::aws:policy/AWSGlueConsoleFullAccess", + "AttachmentCount": 0, + "CreateDate": "2017-09-13T00:12:54+00:00", + "DefaultVersionId": "v2", + "Document": { + "Statement": [ + { + "Action": [ + "glue:*", + "redshift:DescribeClusters", + "redshift:DescribeClusterSubnetGroups", + "iam:ListRoles", + "iam:ListRolePolicies", + "iam:GetRole", + "iam:GetRolePolicy", + "ec2:DescribeSecurityGroups", + "ec2:DescribeSubnets", + "ec2:DescribeVpcs", + "ec2:DescribeVpcEndpoints", + "ec2:DescribeRouteTables", + "ec2:DescribeVpcAttribute", + "ec2:DescribeKeyPairs", + "ec2:DescribeInstances", + "rds:DescribeDBInstances", + "s3:ListAllMyBuckets", + "s3:ListBucket", + "s3:GetBucketAcl", + "s3:GetBucketLocation", + "cloudformation:DescribeStacks", + "cloudformation:GetTemplateSummary" + ], + "Effect": "Allow", + "Resource": [ + "*" + ] + }, + { + "Action": [ + "s3:GetObject", + "s3:PutObject" + ], + "Effect": "Allow", + "Resource": [ + "arn:aws:s3:::aws-glue-*/*", + "arn:aws:s3:::*/*aws-glue-*/*", + "arn:aws:s3:::aws-glue-*" + ] + }, + { + "Action": [ + "s3:CreateBucket" + ], + "Effect": "Allow", + "Resource": [ + "arn:aws:s3:::aws-glue-*" + ] + }, + { + "Action": [ + "logs:GetLogEvents" + ], + "Effect": "Allow", + "Resource": [ + "arn:aws:logs:*:*:/aws-glue/*" + ] + }, + { + "Action": [ + "cloudformation:CreateStack", + "cloudformation:DeleteStack" + ], + "Effect": "Allow", + "Resource": "arn:aws:cloudformation:*:*:stack/aws-glue*/*" + }, + { + "Action": [ + "ec2:TerminateInstances", + "ec2:RunInstances", + "ec2:CreateTags", + "ec2:DeleteTags" + ], + "Condition": { + "ForAllValues:StringEquals": { + "aws:TagKeys": [ + "aws-glue-dev-endpoint" + ] + } + }, + "Effect": "Allow", + "Resource": [ + "*" + ] + }, + { + "Action": [ + "iam:PassRole" + ], + "Condition": { + "StringLike": { + "iam:PassedToService": [ + "glue.amazonaws.com" + ] + } + }, + "Effect": "Allow", + "Resource": "arn:aws:iam::*:role/AWSGlueServiceRole*" + }, + { + "Action": [ + "iam:PassRole" + ], + "Condition": { + "StringLike": { + "iam:PassedToService": [ + "ec2.amazonaws.com" + ] + } + }, + "Effect": "Allow", + "Resource": "arn:aws:iam::*:role/AWSGlueServiceNotebookRole*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAJNZGDEOD7MISOVSVI", + "PolicyName": "AWSGlueConsoleFullAccess", + "UpdateDate": "2017-09-13T00:12:54+00:00", + "VersionId": "v2" + }, + "AWSGlueServiceNotebookRole": { + "Arn": "arn:aws:iam::aws:policy/service-role/AWSGlueServiceNotebookRole", + "AttachmentCount": 0, + "CreateDate": "2017-08-17T18:08:29+00:00", + "DefaultVersionId": "v2", + "Document": { + "Statement": [ + { + "Action": [ + "glue:CreateDatabase", + "glue:CreatePartition", + "glue:CreateTable", + "glue:DeleteDatabase", + "glue:DeletePartition", + "glue:DeleteTable", + "glue:GetDatabase", + "glue:GetDatabases", + "glue:GetPartition", + "glue:GetPartitions", + "glue:GetTable", + "glue:GetTableVersions", + "glue:GetTables", + "glue:UpdateDatabase", + "glue:UpdatePartition", + "glue:UpdateTable", + "glue:CreateBookmark", + "glue:GetBookmark", + "glue:UpdateBookmark", + "glue:GetMetric", + "glue:PutMetric", + "glue:CreateConnection", + "glue:CreateJob", + "glue:DeleteConnection", + "glue:DeleteJob", + "glue:GetConnection", + "glue:GetConnections", + "glue:GetDevEndpoint", + "glue:GetDevEndpoints", + "glue:GetJob", + "glue:GetJobs", + "glue:UpdateJob", + "glue:BatchDeleteConnection", + "glue:UpdateConnection", + "glue:GetUserDefinedFunction", + "glue:UpdateUserDefinedFunction", + "glue:GetUserDefinedFunctions", + "glue:DeleteUserDefinedFunction", + "glue:CreateUserDefinedFunction", + "glue:BatchGetPartition", + "glue:BatchDeletePartition", + "glue:BatchCreatePartition", + "glue:BatchDeleteTable", + "glue:UpdateDevEndpoint", + "s3:GetBucketLocation", + "s3:ListBucket", + "s3:ListAllMyBuckets", + "s3:GetBucketAcl" + ], + "Effect": "Allow", + "Resource": [ + "*" + ] + }, + { + "Action": [ + "s3:GetObject" + ], + "Effect": "Allow", + "Resource": [ + "arn:aws:s3:::crawler-public*", + "arn:aws:s3:::aws-glue*" + ] + }, + { + "Action": [ + "s3:PutObject", + "s3:DeleteObject" + ], + "Effect": "Allow", + "Resource": [ + "arn:aws:s3:::aws-glue*" + ] + }, + { + "Action": [ + "ec2:CreateTags", + "ec2:DeleteTags" + ], + "Condition": { + "ForAllValues:StringEquals": { + "aws:TagKeys": [ + "aws-glue-service-resource" + ] + } + }, + "Effect": "Allow", + "Resource": [ + "arn:aws:ec2:*:*:network-interface/*", + "arn:aws:ec2:*:*:security-group/*", + "arn:aws:ec2:*:*:instance/*" + ] + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/service-role/", + "PolicyId": "ANPAIMRC6VZUHJYCTKWFI", + "PolicyName": "AWSGlueServiceNotebookRole", + "UpdateDate": "2017-08-17T18:08:29+00:00", + "VersionId": "v2" + }, + "AWSGlueServiceRole": { + "Arn": "arn:aws:iam::aws:policy/service-role/AWSGlueServiceRole", + "AttachmentCount": 0, + "CreateDate": "2017-08-23T21:35:25+00:00", + "DefaultVersionId": "v3", + "Document": { + "Statement": [ + { + "Action": [ + "glue:*", + "s3:GetBucketLocation", + "s3:ListBucket", + "s3:ListAllMyBuckets", + "s3:GetBucketAcl", + "ec2:DescribeVpcEndpoints", + "ec2:DescribeRouteTables", + "ec2:CreateNetworkInterface", + "ec2:DeleteNetworkInterface", + "ec2:DescribeNetworkInterfaces", + "ec2:DescribeSecurityGroups", + "ec2:DescribeSubnets", + "ec2:DescribeVpcAttribute", + "iam:ListRolePolicies", + "iam:GetRole", + "iam:GetRolePolicy" + ], + "Effect": "Allow", + "Resource": [ + "*" + ] + }, + { + "Action": [ + "s3:CreateBucket" + ], + "Effect": "Allow", + "Resource": [ + "arn:aws:s3:::aws-glue-*" + ] + }, + { + "Action": [ + "s3:GetObject", + "s3:PutObject", + "s3:DeleteObject" + ], + "Effect": "Allow", + "Resource": [ + "arn:aws:s3:::aws-glue-*/*", + "arn:aws:s3:::*/*aws-glue-*/*" + ] + }, + { + "Action": [ + "s3:GetObject" + ], + "Effect": "Allow", + "Resource": [ + "arn:aws:s3:::crawler-public*", + "arn:aws:s3:::aws-glue-*" + ] + }, + { + "Action": [ + "logs:CreateLogGroup", + "logs:CreateLogStream", + "logs:PutLogEvents" + ], + "Effect": "Allow", + "Resource": [ + "arn:aws:logs:*:*:/aws-glue/*" + ] + }, + { + "Action": [ + "ec2:CreateTags", + "ec2:DeleteTags" + ], + "Condition": { + "ForAllValues:StringEquals": { + "aws:TagKeys": [ + "aws-glue-service-resource" + ] + } + }, + "Effect": "Allow", + "Resource": [ + "arn:aws:ec2:*:*:network-interface/*", + "arn:aws:ec2:*:*:security-group/*", + "arn:aws:ec2:*:*:instance/*" + ] + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/service-role/", + "PolicyId": "ANPAIRUJCPEBPMEZFAS32", + "PolicyName": "AWSGlueServiceRole", + "UpdateDate": "2017-08-23T21:35:25+00:00", + "VersionId": "v3" + }, + "AWSGreengrassFullAccess": { + "Arn": "arn:aws:iam::aws:policy/AWSGreengrassFullAccess", + "AttachmentCount": 0, + "CreateDate": "2017-05-03T00:47:37+00:00", + "DefaultVersionId": "v1", + "Document": { + "Statement": [ + { + "Action": [ + "greengrass:*" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAJWPV6OBK4QONH4J3O", + "PolicyName": "AWSGreengrassFullAccess", + "UpdateDate": "2017-05-03T00:47:37+00:00", + "VersionId": "v1" + }, + "AWSGreengrassResourceAccessRolePolicy": { + "Arn": "arn:aws:iam::aws:policy/service-role/AWSGreengrassResourceAccessRolePolicy", + "AttachmentCount": 0, + "CreateDate": "2017-05-26T23:10:54+00:00", + "DefaultVersionId": "v3", + "Document": { + "Statement": [ + { + "Action": [ + "iot:DeleteThingShadow", + "iot:GetThingShadow", + "iot:UpdateThingShadow" + ], + "Effect": "Allow", + "Resource": [ + "arn:aws:iot:*:*:thing/GG_*", + "arn:aws:iot:*:*:thing/*-gcm", + "arn:aws:iot:*:*:thing/*-gda", + "arn:aws:iot:*:*:thing/*-gci" + ], + "Sid": "AllowGreengrassAccessToShadows" + }, + { + "Action": [ + "iot:DescribeThing" + ], + "Effect": "Allow", + "Resource": "arn:aws:iot:*:*:thing/*", + "Sid": "AllowGreengrassToDescribeThings" + }, + { + "Action": [ + "iot:DescribeCertificate" + ], + "Effect": "Allow", + "Resource": "arn:aws:iot:*:*:cert/*", + "Sid": "AllowGreengrassToDescribeCertificates" + }, + { + "Action": [ + "greengrass:*" + ], + "Effect": "Allow", + "Resource": "*", + "Sid": "AllowGreengrassToCallGreengrassServices" + }, + { + "Action": [ + "lambda:GetFunction", + "lambda:GetFunctionConfiguration" + ], + "Effect": "Allow", + "Resource": "*", + "Sid": "AllowGreengrassToGetLambdaFunctions" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/service-role/", + "PolicyId": "ANPAJPKEIMB6YMXDEVRTM", + "PolicyName": "AWSGreengrassResourceAccessRolePolicy", + "UpdateDate": "2017-05-26T23:10:54+00:00", + "VersionId": "v3" + }, + "AWSHealthFullAccess": { + "Arn": "arn:aws:iam::aws:policy/AWSHealthFullAccess", + "AttachmentCount": 0, + "CreateDate": "2016-12-06T12:30:31+00:00", + "DefaultVersionId": "v1", + "Document": { + "Statement": [ + { + "Action": [ + "health:*" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAI3CUMPCPEUPCSXC4Y", + "PolicyName": "AWSHealthFullAccess", + "UpdateDate": "2016-12-06T12:30:31+00:00", + "VersionId": "v1" + }, + "AWSImportExportFullAccess": { + "Arn": "arn:aws:iam::aws:policy/AWSImportExportFullAccess", + "AttachmentCount": 0, + "CreateDate": "2015-02-06T18:40:43+00:00", + "DefaultVersionId": "v1", + "Document": { + "Statement": [ + { + "Action": [ + "importexport:*" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAJCQCT4JGTLC6722MQ", + "PolicyName": "AWSImportExportFullAccess", + "UpdateDate": "2015-02-06T18:40:43+00:00", + "VersionId": "v1" + }, + "AWSImportExportReadOnlyAccess": { + "Arn": "arn:aws:iam::aws:policy/AWSImportExportReadOnlyAccess", + "AttachmentCount": 0, + "CreateDate": "2015-02-06T18:40:42+00:00", + "DefaultVersionId": "v1", + "Document": { + "Statement": [ + { + "Action": [ + "importexport:ListJobs", + "importexport:GetStatus" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAJNTV4OG52ESYZHCNK", + "PolicyName": "AWSImportExportReadOnlyAccess", + "UpdateDate": "2015-02-06T18:40:42+00:00", + "VersionId": "v1" + }, + "AWSIoTConfigAccess": { + "Arn": "arn:aws:iam::aws:policy/AWSIoTConfigAccess", + "AttachmentCount": 0, + "CreateDate": "2016-07-27T20:41:18+00:00", + "DefaultVersionId": "v4", + "Document": { + "Statement": [ + { + "Action": [ + "iot:AcceptCertificateTransfer", + "iot:AttachPrincipalPolicy", + "iot:AttachThingPrincipal", + "iot:CancelCertificateTransfer", + "iot:CreateCertificateFromCsr", + "iot:CreateKeysAndCertificate", + "iot:CreatePolicy", + "iot:CreatePolicyVersion", + "iot:CreateThing", + "iot:CreateThingType", + "iot:CreateTopicRule", + "iot:DeleteCertificate", + "iot:DeleteCACertificate", + "iot:DeletePolicy", + "iot:DeletePolicyVersion", + "iot:DeleteRegistrationCode", + "iot:DeleteThing", + "iot:DeleteThingType", + "iot:DeleteTopicRule", + "iot:DeprecateThingType", + "iot:DescribeCertificate", + "iot:DescribeCACertificate", + "iot:DescribeEndpoint", + "iot:DescribeThing", + "iot:DescribeThingType", + "iot:DetachPrincipalPolicy", + "iot:DetachThingPrincipal", + "iot:GetLoggingOptions", + "iot:GetPolicy", + "iot:GetPolicyVersion", + "iot:GetRegistrationCode", + "iot:GetTopicRule", + "iot:ListCertificates", + "iot:ListCACertificates", + "iot:ListCertificatesByCA", + "iot:ListPolicies", + "iot:ListPolicyPrincipals", + "iot:ListPolicyVersions", + "iot:ListPrincipalPolicies", + "iot:ListPrincipalThings", + "iot:ListThingPrincipals", + "iot:ListThings", + "iot:ListThingTypes", + "iot:ListTopicRules", + "iot:RegisterCertificate", + "iot:RegisterCACertificate", + "iot:RejectCertificateTransfer", + "iot:ReplaceTopicRule", + "iot:SetDefaultPolicyVersion", + "iot:SetLoggingOptions", + "iot:TransferCertificate", + "iot:UpdateCertificate", + "iot:UpdateCACertificate", + "iot:UpdateThing" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAIWWGD4LM4EMXNRL7I", + "PolicyName": "AWSIoTConfigAccess", + "UpdateDate": "2016-07-27T20:41:18+00:00", + "VersionId": "v4" + }, + "AWSIoTConfigReadOnlyAccess": { + "Arn": "arn:aws:iam::aws:policy/AWSIoTConfigReadOnlyAccess", + "AttachmentCount": 0, + "CreateDate": "2016-07-27T20:41:36+00:00", + "DefaultVersionId": "v4", + "Document": { + "Statement": [ + { + "Action": [ + "iot:DescribeCertificate", + "iot:DescribeCACertificate", + "iot:DescribeEndpoint", + "iot:DescribeThing", + "iot:DescribeThingType", + "iot:GetLoggingOptions", + "iot:GetPolicy", + "iot:GetPolicyVersion", + "iot:GetRegistrationCode", + "iot:GetTopicRule", + "iot:ListCertificates", + "iot:ListCertificatesByCA", + "iot:ListCACertificates", + "iot:ListPolicies", + "iot:ListPolicyPrincipals", + "iot:ListPolicyVersions", + "iot:ListPrincipalPolicies", + "iot:ListPrincipalThings", + "iot:ListThingPrincipals", + "iot:ListThings", + "iot:ListThingTypes", + "iot:ListTopicRules" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAJHENEMXGX4XMFOIOI", + "PolicyName": "AWSIoTConfigReadOnlyAccess", + "UpdateDate": "2016-07-27T20:41:36+00:00", + "VersionId": "v4" + }, + "AWSIoTDataAccess": { + "Arn": "arn:aws:iam::aws:policy/AWSIoTDataAccess", + "AttachmentCount": 0, + "CreateDate": "2015-10-27T21:51:18+00:00", + "DefaultVersionId": "v1", + "Document": { + "Statement": [ + { + "Action": [ + "iot:Connect", + "iot:Publish", + "iot:Subscribe", + "iot:Receive", + "iot:GetThingShadow", + "iot:UpdateThingShadow" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAJM2KI2UJDR24XPS2K", + "PolicyName": "AWSIoTDataAccess", + "UpdateDate": "2015-10-27T21:51:18+00:00", + "VersionId": "v1" + }, + "AWSIoTFullAccess": { + "Arn": "arn:aws:iam::aws:policy/AWSIoTFullAccess", + "AttachmentCount": 0, + "CreateDate": "2015-10-08T15:19:49+00:00", + "DefaultVersionId": "v1", + "Document": { + "Statement": [ + { + "Action": [ + "iot:*" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAJU2FPGG6PQWN72V2G", + "PolicyName": "AWSIoTFullAccess", + "UpdateDate": "2015-10-08T15:19:49+00:00", + "VersionId": "v1" + }, + "AWSIoTLogging": { + "Arn": "arn:aws:iam::aws:policy/service-role/AWSIoTLogging", + "AttachmentCount": 0, + "CreateDate": "2015-10-08T15:17:25+00:00", + "DefaultVersionId": "v1", + "Document": { + "Statement": [ + { + "Action": [ + "logs:CreateLogGroup", + "logs:CreateLogStream", + "logs:PutLogEvents", + "logs:PutMetricFilter", + "logs:PutRetentionPolicy", + "logs:GetLogEvents", + "logs:DeleteLogStream" + ], + "Effect": "Allow", + "Resource": [ + "*" + ] + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/service-role/", + "PolicyId": "ANPAI6R6Z2FHHGS454W7W", + "PolicyName": "AWSIoTLogging", + "UpdateDate": "2015-10-08T15:17:25+00:00", + "VersionId": "v1" + }, + "AWSIoTRuleActions": { + "Arn": "arn:aws:iam::aws:policy/service-role/AWSIoTRuleActions", + "AttachmentCount": 0, + "CreateDate": "2015-10-08T15:14:51+00:00", + "DefaultVersionId": "v1", + "Document": { + "Statement": { + "Action": [ + "dynamodb:PutItem", + "kinesis:PutRecord", + "iot:Publish", + "s3:PutObject", + "sns:Publish", + "sqs:SendMessage*" + ], + "Effect": "Allow", + "Resource": "*" + }, + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/service-role/", + "PolicyId": "ANPAJEZ6FS7BUZVUHMOKY", + "PolicyName": "AWSIoTRuleActions", + "UpdateDate": "2015-10-08T15:14:51+00:00", + "VersionId": "v1" + }, + "AWSKeyManagementServicePowerUser": { + "Arn": "arn:aws:iam::aws:policy/AWSKeyManagementServicePowerUser", + "AttachmentCount": 1, + "CreateDate": "2017-03-07T00:55:11+00:00", + "DefaultVersionId": "v2", + "Document": { + "Statement": [ + { + "Action": [ + "kms:CreateAlias", + "kms:CreateKey", + "kms:DeleteAlias", + "kms:Describe*", + "kms:GenerateRandom", + "kms:Get*", + "kms:List*", + "kms:TagResource", + "kms:UntagResource", + "iam:ListGroups", + "iam:ListRoles", + "iam:ListUsers" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAJNPP7PPPPMJRV2SA4", + "PolicyName": "AWSKeyManagementServicePowerUser", + "UpdateDate": "2017-03-07T00:55:11+00:00", + "VersionId": "v2" + }, + "AWSLambdaBasicExecutionRole": { + "Arn": "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole", + "AttachmentCount": 0, + "CreateDate": "2015-04-09T15:03:43+00:00", + "DefaultVersionId": "v1", + "Document": { + "Statement": [ + { + "Action": [ + "logs:CreateLogGroup", + "logs:CreateLogStream", + "logs:PutLogEvents" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/service-role/", + "PolicyId": "ANPAJNCQGXC42545SKXIK", + "PolicyName": "AWSLambdaBasicExecutionRole", + "UpdateDate": "2015-04-09T15:03:43+00:00", + "VersionId": "v1" + }, + "AWSLambdaDynamoDBExecutionRole": { + "Arn": "arn:aws:iam::aws:policy/service-role/AWSLambdaDynamoDBExecutionRole", + "AttachmentCount": 0, + "CreateDate": "2015-04-09T15:09:29+00:00", + "DefaultVersionId": "v1", + "Document": { + "Statement": [ + { + "Action": [ + "dynamodb:DescribeStream", + "dynamodb:GetRecords", + "dynamodb:GetShardIterator", + "dynamodb:ListStreams", + "logs:CreateLogGroup", + "logs:CreateLogStream", + "logs:PutLogEvents" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/service-role/", + "PolicyId": "ANPAIP7WNAGMIPYNW4WQG", + "PolicyName": "AWSLambdaDynamoDBExecutionRole", + "UpdateDate": "2015-04-09T15:09:29+00:00", + "VersionId": "v1" + }, + "AWSLambdaENIManagementAccess": { + "Arn": "arn:aws:iam::aws:policy/service-role/AWSLambdaENIManagementAccess", + "AttachmentCount": 0, + "CreateDate": "2016-12-06T00:37:27+00:00", + "DefaultVersionId": "v1", + "Document": { + "Statement": [ + { + "Action": [ + "ec2:CreateNetworkInterface", + "ec2:DescribeNetworkInterfaces", + "ec2:DeleteNetworkInterface" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/service-role/", + "PolicyId": "ANPAJXAW2Q3KPTURUT2QC", + "PolicyName": "AWSLambdaENIManagementAccess", + "UpdateDate": "2016-12-06T00:37:27+00:00", + "VersionId": "v1" + }, + "AWSLambdaExecute": { + "Arn": "arn:aws:iam::aws:policy/AWSLambdaExecute", + "AttachmentCount": 0, + "CreateDate": "2015-02-06T18:40:46+00:00", + "DefaultVersionId": "v1", + "Document": { + "Statement": [ + { + "Action": [ + "logs:*" + ], + "Effect": "Allow", + "Resource": "arn:aws:logs:*:*:*" + }, + { + "Action": [ + "s3:GetObject", + "s3:PutObject" + ], + "Effect": "Allow", + "Resource": "arn:aws:s3:::*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAJE5FX7FQZSU5XAKGO", + "PolicyName": "AWSLambdaExecute", + "UpdateDate": "2015-02-06T18:40:46+00:00", + "VersionId": "v1" + }, + "AWSLambdaFullAccess": { + "Arn": "arn:aws:iam::aws:policy/AWSLambdaFullAccess", + "AttachmentCount": 0, + "CreateDate": "2017-05-25T19:08:45+00:00", + "DefaultVersionId": "v7", + "Document": { + "Statement": [ + { + "Action": [ + "cloudwatch:*", + "cognito-identity:ListIdentityPools", + "cognito-sync:GetCognitoEvents", + "cognito-sync:SetCognitoEvents", + "dynamodb:*", + "events:*", + "iam:ListAttachedRolePolicies", + "iam:ListRolePolicies", + "iam:ListRoles", + "iam:PassRole", + "kinesis:DescribeStream", + "kinesis:ListStreams", + "kinesis:PutRecord", + "lambda:*", + "logs:*", + "s3:*", + "sns:ListSubscriptions", + "sns:ListSubscriptionsByTopic", + "sns:ListTopics", + "sns:Subscribe", + "sns:Unsubscribe", + "sns:Publish", + "sqs:ListQueues", + "sqs:SendMessage", + "tag:GetResources", + "kms:ListAliases", + "ec2:DescribeVpcs", + "ec2:DescribeSubnets", + "ec2:DescribeSecurityGroups", + "iot:GetTopicRule", + "iot:ListTopicRules", + "iot:CreateTopicRule", + "iot:ReplaceTopicRule", + "iot:AttachPrincipalPolicy", + "iot:AttachThingPrincipal", + "iot:CreateKeysAndCertificate", + "iot:CreatePolicy", + "iot:CreateThing", + "iot:ListPolicies", + "iot:ListThings", + "iot:DescribeEndpoint", + "xray:PutTraceSegments", + "xray:PutTelemetryRecords" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAI6E2CYYMI4XI7AA5K", + "PolicyName": "AWSLambdaFullAccess", + "UpdateDate": "2017-05-25T19:08:45+00:00", + "VersionId": "v7" + }, + "AWSLambdaInvocation-DynamoDB": { + "Arn": "arn:aws:iam::aws:policy/AWSLambdaInvocation-DynamoDB", + "AttachmentCount": 0, + "CreateDate": "2015-02-06T18:40:47+00:00", + "DefaultVersionId": "v1", + "Document": { + "Statement": [ + { + "Action": [ + "lambda:InvokeFunction" + ], + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": [ + "dynamodb:DescribeStream", + "dynamodb:GetRecords", + "dynamodb:GetShardIterator", + "dynamodb:ListStreams" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAJTHQ3EKCQALQDYG5G", + "PolicyName": "AWSLambdaInvocation-DynamoDB", + "UpdateDate": "2015-02-06T18:40:47+00:00", + "VersionId": "v1" + }, + "AWSLambdaKinesisExecutionRole": { + "Arn": "arn:aws:iam::aws:policy/service-role/AWSLambdaKinesisExecutionRole", + "AttachmentCount": 0, + "CreateDate": "2015-04-09T15:14:16+00:00", + "DefaultVersionId": "v1", + "Document": { + "Statement": [ + { + "Action": [ + "kinesis:DescribeStream", + "kinesis:GetRecords", + "kinesis:GetShardIterator", + "kinesis:ListStreams", + "logs:CreateLogGroup", + "logs:CreateLogStream", + "logs:PutLogEvents" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/service-role/", + "PolicyId": "ANPAJHOLKJPXV4GBRMJUQ", + "PolicyName": "AWSLambdaKinesisExecutionRole", + "UpdateDate": "2015-04-09T15:14:16+00:00", + "VersionId": "v1" + }, + "AWSLambdaReadOnlyAccess": { + "Arn": "arn:aws:iam::aws:policy/AWSLambdaReadOnlyAccess", + "AttachmentCount": 0, + "CreateDate": "2017-05-04T18:22:29+00:00", + "DefaultVersionId": "v6", + "Document": { + "Statement": [ + { + "Action": [ + "cloudwatch:Describe*", + "cloudwatch:Get*", + "cloudwatch:List*", + "cognito-identity:ListIdentityPools", + "cognito-sync:GetCognitoEvents", + "dynamodb:BatchGetItem", + "dynamodb:DescribeStream", + "dynamodb:DescribeTable", + "dynamodb:GetItem", + "dynamodb:ListStreams", + "dynamodb:ListTables", + "dynamodb:Query", + "dynamodb:Scan", + "events:List*", + "events:Describe*", + "iam:ListRoles", + "kinesis:DescribeStream", + "kinesis:ListStreams", + "lambda:List*", + "lambda:Get*", + "logs:DescribeMetricFilters", + "logs:GetLogEvents", + "logs:DescribeLogGroups", + "logs:DescribeLogStreams", + "s3:Get*", + "s3:List*", + "sns:ListTopics", + "sns:ListSubscriptions", + "sns:ListSubscriptionsByTopic", + "sqs:ListQueues", + "tag:GetResources", + "kms:ListAliases", + "ec2:DescribeVpcs", + "ec2:DescribeSubnets", + "ec2:DescribeSecurityGroups", + "iot:GetTopicRules", + "iot:ListTopicRules", + "iot:ListPolicies", + "iot:ListThings", + "iot:DescribeEndpoint" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAJLDG7J3CGUHFN4YN6", + "PolicyName": "AWSLambdaReadOnlyAccess", + "UpdateDate": "2017-05-04T18:22:29+00:00", + "VersionId": "v6" + }, + "AWSLambdaRole": { + "Arn": "arn:aws:iam::aws:policy/service-role/AWSLambdaRole", + "AttachmentCount": 0, + "CreateDate": "2015-02-06T18:41:28+00:00", + "DefaultVersionId": "v1", + "Document": { + "Statement": [ + { + "Action": [ + "lambda:InvokeFunction" + ], + "Effect": "Allow", + "Resource": [ + "*" + ] + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/service-role/", + "PolicyId": "ANPAJX4DPCRGTC4NFDUXI", + "PolicyName": "AWSLambdaRole", + "UpdateDate": "2015-02-06T18:41:28+00:00", + "VersionId": "v1" + }, + "AWSLambdaVPCAccessExecutionRole": { + "Arn": "arn:aws:iam::aws:policy/service-role/AWSLambdaVPCAccessExecutionRole", + "AttachmentCount": 0, + "CreateDate": "2016-02-11T23:15:26+00:00", + "DefaultVersionId": "v1", + "Document": { + "Statement": [ + { + "Action": [ + "logs:CreateLogGroup", + "logs:CreateLogStream", + "logs:PutLogEvents", + "ec2:CreateNetworkInterface", + "ec2:DescribeNetworkInterfaces", + "ec2:DeleteNetworkInterface" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/service-role/", + "PolicyId": "ANPAJVTME3YLVNL72YR2K", + "PolicyName": "AWSLambdaVPCAccessExecutionRole", + "UpdateDate": "2016-02-11T23:15:26+00:00", + "VersionId": "v1" + }, + "AWSMarketplaceFullAccess": { + "Arn": "arn:aws:iam::aws:policy/AWSMarketplaceFullAccess", + "AttachmentCount": 0, + "CreateDate": "2015-02-11T17:21:45+00:00", + "DefaultVersionId": "v1", + "Document": { + "Statement": [ + { + "Action": [ + "aws-marketplace:*", + "cloudformation:CreateStack", + "cloudformation:DescribeStackResource", + "cloudformation:DescribeStackResources", + "cloudformation:DescribeStacks", + "cloudformation:List*", + "ec2:AuthorizeSecurityGroupEgress", + "ec2:AuthorizeSecurityGroupIngress", + "ec2:CreateSecurityGroup", + "ec2:CreateTags", + "ec2:DescribeAccountAttributes", + "ec2:DescribeAddresses", + "ec2:DeleteSecurityGroup", + "ec2:DescribeAccountAttributes", + "ec2:DescribeImages", + "ec2:DescribeInstances", + "ec2:DescribeKeyPairs", + "ec2:DescribeSecurityGroups", + "ec2:DescribeSubnets", + "ec2:DescribeTags", + "ec2:DescribeVpcs", + "ec2:RunInstances", + "ec2:StartInstances", + "ec2:StopInstances", + "ec2:TerminateInstances" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAI2DV5ULJSO2FYVPYG", + "PolicyName": "AWSMarketplaceFullAccess", + "UpdateDate": "2015-02-11T17:21:45+00:00", + "VersionId": "v1" + }, + "AWSMarketplaceGetEntitlements": { + "Arn": "arn:aws:iam::aws:policy/AWSMarketplaceGetEntitlements", + "AttachmentCount": 0, + "CreateDate": "2017-03-27T19:37:24+00:00", + "DefaultVersionId": "v1", + "Document": { + "Statement": [ + { + "Action": [ + "aws-marketplace:GetEntitlements" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAJLPIMQE4WMHDC2K7C", + "PolicyName": "AWSMarketplaceGetEntitlements", + "UpdateDate": "2017-03-27T19:37:24+00:00", + "VersionId": "v1" + }, + "AWSMarketplaceManageSubscriptions": { + "Arn": "arn:aws:iam::aws:policy/AWSMarketplaceManageSubscriptions", + "AttachmentCount": 0, + "CreateDate": "2015-02-06T18:40:32+00:00", + "DefaultVersionId": "v1", + "Document": { + "Statement": [ + { + "Action": [ + "aws-marketplace:ViewSubscriptions", + "aws-marketplace:Subscribe", + "aws-marketplace:Unsubscribe" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAJRDW2WIFN7QLUAKBQ", + "PolicyName": "AWSMarketplaceManageSubscriptions", + "UpdateDate": "2015-02-06T18:40:32+00:00", + "VersionId": "v1" + }, + "AWSMarketplaceMeteringFullAccess": { + "Arn": "arn:aws:iam::aws:policy/AWSMarketplaceMeteringFullAccess", + "AttachmentCount": 0, + "CreateDate": "2016-03-17T22:39:22+00:00", + "DefaultVersionId": "v1", + "Document": { + "Statement": [ + { + "Action": [ + "aws-marketplace:MeterUsage" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAJ65YJPG7CC7LDXNA6", + "PolicyName": "AWSMarketplaceMeteringFullAccess", + "UpdateDate": "2016-03-17T22:39:22+00:00", + "VersionId": "v1" + }, + "AWSMarketplaceRead-only": { + "Arn": "arn:aws:iam::aws:policy/AWSMarketplaceRead-only", + "AttachmentCount": 0, + "CreateDate": "2015-02-06T18:40:31+00:00", + "DefaultVersionId": "v1", + "Document": { + "Statement": [ + { + "Action": [ + "aws-marketplace:ViewSubscriptions", + "ec2:DescribeAccountAttributes", + "ec2:DescribeAddresses", + "ec2:DescribeImages", + "ec2:DescribeInstances", + "ec2:DescribeKeyPairs", + "ec2:DescribeSecurityGroups", + "ec2:DescribeSubnets", + "ec2:DescribeVpcs" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAJOOM6LETKURTJ3XZ2", + "PolicyName": "AWSMarketplaceRead-only", + "UpdateDate": "2015-02-06T18:40:31+00:00", + "VersionId": "v1" + }, + "AWSMigrationHubDMSAccess": { + "Arn": "arn:aws:iam::aws:policy/service-role/AWSMigrationHubDMSAccess", + "AttachmentCount": 0, + "CreateDate": "2017-08-14T14:00:06+00:00", + "DefaultVersionId": "v1", + "Document": { + "Statement": [ + { + "Action": [ + "mgh:CreateProgressUpdateStream" + ], + "Effect": "Allow", + "Resource": "arn:aws:mgh:*:*:progressUpdateStream/DMS" + }, + { + "Action": [ + "mgh:AssociateCreatedArtifact", + "mgh:DescribeMigrationTask", + "mgh:DisassociateCreatedArtifact", + "mgh:ImportMigrationTask", + "mgh:ListCreatedArtifacts", + "mgh:NotifyMigrationTaskState", + "mgh:PutResourceAttributes", + "mgh:NotifyApplicationState", + "mgh:DescribeApplicationState", + "mgh:AssociateDiscoveredResource", + "mgh:DisassociateDiscoveredResource", + "mgh:ListDiscoveredResources" + ], + "Effect": "Allow", + "Resource": "arn:aws:mgh:*:*:progressUpdateStream/DMS/*" + }, + { + "Action": [ + "mgh:ListMigrationTasks" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/service-role/", + "PolicyId": "ANPAIUQB56VA4JHLN7G2W", + "PolicyName": "AWSMigrationHubDMSAccess", + "UpdateDate": "2017-08-14T14:00:06+00:00", + "VersionId": "v1" + }, + "AWSMigrationHubDiscoveryAccess": { + "Arn": "arn:aws:iam::aws:policy/service-role/AWSMigrationHubDiscoveryAccess", + "AttachmentCount": 0, + "CreateDate": "2017-08-14T13:30:51+00:00", + "DefaultVersionId": "v1", + "Document": { + "Statement": [ + { + "Action": [ + "discovery:ListConfigurations", + "discovery:DescribeConfigurations" + ], + "Effect": "Allow", + "Resource": [ + "*" + ] + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/service-role/", + "PolicyId": "ANPAITRMRLSV7JAL6YIGG", + "PolicyName": "AWSMigrationHubDiscoveryAccess", + "UpdateDate": "2017-08-14T13:30:51+00:00", + "VersionId": "v1" + }, + "AWSMigrationHubFullAccess": { + "Arn": "arn:aws:iam::aws:policy/AWSMigrationHubFullAccess", + "AttachmentCount": 0, + "CreateDate": "2017-08-14T14:09:27+00:00", + "DefaultVersionId": "v2", + "Document": { + "Statement": [ + { + "Action": [ + "mgh:*", + "discovery:*" + ], + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": [ + "iam:GetRole" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAJ4A2SZKHUYHDYIGOK", + "PolicyName": "AWSMigrationHubFullAccess", + "UpdateDate": "2017-08-14T14:09:27+00:00", + "VersionId": "v2" + }, + "AWSMigrationHubSMSAccess": { + "Arn": "arn:aws:iam::aws:policy/service-role/AWSMigrationHubSMSAccess", + "AttachmentCount": 0, + "CreateDate": "2017-08-14T13:57:54+00:00", + "DefaultVersionId": "v1", + "Document": { + "Statement": [ + { + "Action": [ + "mgh:CreateProgressUpdateStream" + ], + "Effect": "Allow", + "Resource": "arn:aws:mgh:*:*:progressUpdateStream/SMS" + }, + { + "Action": [ + "mgh:AssociateCreatedArtifact", + "mgh:DescribeMigrationTask", + "mgh:DisassociateCreatedArtifact", + "mgh:ImportMigrationTask", + "mgh:ListCreatedArtifacts", + "mgh:NotifyMigrationTaskState", + "mgh:PutResourceAttributes", + "mgh:NotifyApplicationState", + "mgh:DescribeApplicationState", + "mgh:AssociateDiscoveredResource", + "mgh:DisassociateDiscoveredResource", + "mgh:ListDiscoveredResources" + ], + "Effect": "Allow", + "Resource": "arn:aws:mgh:*:*:progressUpdateStream/SMS/*" + }, + { + "Action": [ + "mgh:ListMigrationTasks" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/service-role/", + "PolicyId": "ANPAIWQYYT6TSVIRJO4TY", + "PolicyName": "AWSMigrationHubSMSAccess", + "UpdateDate": "2017-08-14T13:57:54+00:00", + "VersionId": "v1" + }, + "AWSMobileHub_FullAccess": { + "Arn": "arn:aws:iam::aws:policy/AWSMobileHub_FullAccess", + "AttachmentCount": 0, + "CreateDate": "2017-08-10T22:23:47+00:00", + "DefaultVersionId": "v10", + "Document": { + "Statement": [ + { + "Action": [ + "apigateway:GET", + "apigateway:GetRestApis", + "apigateway:GetResources", + "apigateway:POST", + "apigateway:TestInvokeMethod", + "dynamodb:DescribeTable", + "ec2:DescribeSecurityGroups", + "ec2:DescribeSubnets", + "ec2:DescribeVpcs", + "iam:ListSAMLProviders", + "lambda:ListFunctions", + "sns:ListTopics", + "lex:GetIntent", + "lex:GetIntents", + "lex:GetSlotType", + "lex:GetSlotTypes", + "lex:GetBot", + "lex:GetBots", + "lex:GetBotAlias", + "lex:GetBotAliases", + "mobilehub:CreateProject", + "mobilehub:DeleteProject", + "mobilehub:UpdateProject", + "mobilehub:ExportProject", + "mobilehub:ImportProject", + "mobilehub:SynchronizeProject", + "mobilehub:GenerateProjectParameters", + "mobilehub:GetProject", + "mobilehub:GetProjectSnapshot", + "mobilehub:ListAvailableConnectors", + "mobilehub:ListAvailableFeatures", + "mobilehub:ListAvailableRegions", + "mobilehub:ListProjects", + "mobilehub:ValidateProject", + "mobilehub:VerifyServiceRole", + "mobilehub:DescribeBundle", + "mobilehub:ExportBundle", + "mobilehub:ListBundles" + ], + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": [ + "s3:GetObject" + ], + "Effect": "Allow", + "Resource": "arn:aws:s3:::*/aws-my-sample-app*.zip" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAIJLU43R6AGRBK76DM", + "PolicyName": "AWSMobileHub_FullAccess", + "UpdateDate": "2017-08-10T22:23:47+00:00", + "VersionId": "v10" + }, + "AWSMobileHub_ReadOnly": { + "Arn": "arn:aws:iam::aws:policy/AWSMobileHub_ReadOnly", + "AttachmentCount": 0, + "CreateDate": "2017-08-10T22:08:23+00:00", + "DefaultVersionId": "v8", + "Document": { + "Statement": [ + { + "Action": [ + "dynamodb:DescribeTable", + "iam:ListSAMLProviders", + "lambda:ListFunctions", + "sns:ListTopics", + "lex:GetIntent", + "lex:GetIntents", + "lex:GetSlotType", + "lex:GetSlotTypes", + "lex:GetBot", + "lex:GetBots", + "lex:GetBotAlias", + "lex:GetBotAliases", + "mobilehub:ExportProject", + "mobilehub:GenerateProjectParameters", + "mobilehub:GetProject", + "mobilehub:GetProjectSnapshot", + "mobilehub:ListAvailableConnectors", + "mobilehub:ListAvailableFeatures", + "mobilehub:ListAvailableRegions", + "mobilehub:ListProjects", + "mobilehub:ValidateProject", + "mobilehub:VerifyServiceRole", + "mobilehub:DescribeBundle", + "mobilehub:ExportBundle", + "mobilehub:ListBundles" + ], + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": [ + "s3:GetObject" + ], + "Effect": "Allow", + "Resource": "arn:aws:s3:::*/aws-my-sample-app*.zip" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAIBXVYVL3PWQFBZFGW", + "PolicyName": "AWSMobileHub_ReadOnly", + "UpdateDate": "2017-08-10T22:08:23+00:00", + "VersionId": "v8" + }, + "AWSMobileHub_ServiceUseOnly": { + "Arn": "arn:aws:iam::aws:policy/service-role/AWSMobileHub_ServiceUseOnly", + "AttachmentCount": 0, + "CreateDate": "2017-06-02T23:35:49+00:00", + "DefaultVersionId": "v23", + "Document": { + "Statement": [ + { + "Action": [ + "cloudformation:CreateUploadBucket", + "cloudformation:ValidateTemplate", + "cloudfront:CreateDistribution", + "cloudfront:DeleteDistribution", + "cloudfront:GetDistribution", + "cloudfront:GetDistributionConfig", + "cloudfront:UpdateDistribution", + "cognito-identity:CreateIdentityPool", + "cognito-identity:UpdateIdentityPool", + "cognito-identity:DeleteIdentityPool", + "cognito-identity:SetIdentityPoolRoles", + "cognito-idp:CreateUserPool", + "dynamodb:CreateTable", + "dynamodb:DeleteTable", + "dynamodb:DescribeTable", + "dynamodb:UpdateTable", + "iam:AddClientIDToOpenIDConnectProvider", + "iam:CreateOpenIDConnectProvider", + "iam:GetOpenIDConnectProvider", + "iam:ListOpenIDConnectProviders", + "iam:CreateSAMLProvider", + "iam:GetSAMLProvider", + "iam:ListSAMLProvider", + "iam:UpdateSAMLProvider", + "lambda:CreateFunction", + "lambda:DeleteFunction", + "lambda:GetFunction", + "mobileanalytics:CreateApp", + "mobileanalytics:DeleteApp", + "sns:CreateTopic", + "sns:DeleteTopic", + "sns:ListPlatformApplications", + "ec2:DescribeSecurityGroups", + "ec2:DescribeSubnets", + "ec2:DescribeVpcs", + "lex:PutIntent", + "lex:GetIntent", + "lex:GetIntents", + "lex:PutSlotType", + "lex:GetSlotType", + "lex:GetSlotTypes", + "lex:PutBot", + "lex:GetBot", + "lex:GetBots", + "lex:GetBotAlias", + "lex:GetBotAliases" + ], + "Effect": "Allow", + "Resource": [ + "*" + ] + }, + { + "Action": [ + "sns:CreatePlatformApplication", + "sns:DeletePlatformApplication", + "sns:GetPlatformApplicationAttributes", + "sns:SetPlatformApplicationAttributes" + ], + "Effect": "Allow", + "Resource": [ + "arn:aws:sns:*:*:app/*_MOBILEHUB_*" + ] + }, + { + "Action": [ + "s3:CreateBucket", + "s3:DeleteBucket", + "s3:DeleteBucketPolicy", + "s3:DeleteBucketWebsite", + "s3:ListBucket", + "s3:ListBucketVersions", + "s3:GetBucketLocation", + "s3:GetBucketVersioning", + "s3:PutBucketVersioning", + "s3:PutBucketWebsite", + "s3:PutBucketPolicy", + "s3:SetBucketCrossOriginConfiguration" + ], + "Effect": "Allow", + "Resource": [ + "arn:aws:s3:::*-userfiles-mobilehub-*", + "arn:aws:s3:::*-contentdelivery-mobilehub-*", + "arn:aws:s3:::*-hosting-mobilehub-*", + "arn:aws:s3:::*-deployments-mobilehub-*" + ] + }, + { + "Action": [ + "s3:DeleteObject", + "s3:DeleteVersion", + "s3:DeleteObjectVersion", + "s3:GetObject", + "s3:GetObjectVersion", + "s3:PutObject", + "s3:PutObjectAcl" + ], + "Effect": "Allow", + "Resource": [ + "arn:aws:s3:::*-userfiles-mobilehub-*/*", + "arn:aws:s3:::*-contentdelivery-mobilehub-*/*", + "arn:aws:s3:::*-hosting-mobilehub-*/*", + "arn:aws:s3:::*-deployments-mobilehub-*/*" + ] + }, + { + "Action": [ + "lambda:AddPermission", + "lambda:CreateAlias", + "lambda:DeleteAlias", + "lambda:UpdateAlias", + "lambda:GetFunctionConfiguration", + "lambda:GetPolicy", + "lambda:RemovePermission", + "lambda:UpdateFunctionCode", + "lambda:UpdateFunctionConfiguration" + ], + "Effect": "Allow", + "Resource": [ + "arn:aws:lambda:*:*:function:*-mobilehub-*" + ] + }, + { + "Action": [ + "iam:CreateRole", + "iam:DeleteRole", + "iam:DeleteRolePolicy", + "iam:GetRole", + "iam:GetRolePolicy", + "iam:ListRolePolicies", + "iam:PassRole", + "iam:PutRolePolicy", + "iam:UpdateAssumeRolePolicy", + "iam:AttachRolePolicy", + "iam:DetachRolePolicy" + ], + "Effect": "Allow", + "Resource": [ + "arn:aws:iam::*:role/*_unauth_MOBILEHUB_*", + "arn:aws:iam::*:role/*_auth_MOBILEHUB_*", + "arn:aws:iam::*:role/*_consolepush_MOBILEHUB_*", + "arn:aws:iam::*:role/*_lambdaexecutionrole_MOBILEHUB_*", + "arn:aws:iam::*:role/*_smsverification_MOBILEHUB_*", + "arn:aws:iam::*:role/*_botexecutionrole_MOBILEHUB_*", + "arn:aws:iam::*:role/pinpoint-events", + "arn:aws:iam::*:role/MOBILEHUB-*-lambdaexecution*", + "arn:aws:iam::*:role/MobileHub_Service_Role" + ] + }, + { + "Action": [ + "iam:CreateServiceLinkedRole", + "iam:GetRole" + ], + "Effect": "Allow", + "Resource": [ + "arn:aws:iam::*:role/aws-service-role/lex.amazonaws.com/AWSServiceRoleForLexBots" + ] + }, + { + "Action": [ + "logs:CreateLogGroup", + "logs:CreateLogStream", + "logs:PutLogEvents" + ], + "Effect": "Allow", + "Resource": [ + "arn:aws:logs:*:*:log-group:/aws/mobilehub/*:log-stream:*" + ] + }, + { + "Action": [ + "iam:ListAttachedRolePolicies" + ], + "Effect": "Allow", + "Resource": [ + "arn:aws:iam::*:role/MobileHub_Service_Role" + ] + }, + { + "Action": [ + "cloudformation:CreateStack", + "cloudformation:DeleteStack", + "cloudformation:DescribeStacks", + "cloudformation:DescribeStackEvents", + "cloudformation:DescribeStackResource", + "cloudformation:GetTemplate", + "cloudformation:ListStackResources", + "cloudformation:ListStacks", + "cloudformation:UpdateStack" + ], + "Effect": "Allow", + "Resource": [ + "arn:aws:cloudformation:*:*:stack/MOBILEHUB-*" + ] + }, + { + "Action": [ + "apigateway:DELETE", + "apigateway:GET", + "apigateway:HEAD", + "apigateway:OPTIONS", + "apigateway:PATCH", + "apigateway:POST", + "apigateway:PUT" + ], + "Effect": "Allow", + "Resource": [ + "arn:aws:apigateway:*::/restapis*" + ] + }, + { + "Action": [ + "cognito-idp:DeleteUserPool", + "cognito-idp:DescribeUserPool", + "cognito-idp:CreateUserPoolClient", + "cognito-idp:DescribeUserPoolClient", + "cognito-idp:DeleteUserPoolClient" + ], + "Effect": "Allow", + "Resource": [ + "arn:aws:cognito-idp:*:*:userpool/*" + ] + }, + { + "Action": [ + "mobiletargeting:UpdateApnsChannel", + "mobiletargeting:UpdateApnsSandboxChannel", + "mobiletargeting:UpdateEmailChannel", + "mobiletargeting:UpdateGcmChannel", + "mobiletargeting:UpdateSmsChannel", + "mobiletargeting:DeleteApnsChannel", + "mobiletargeting:DeleteApnsSandboxChannel", + "mobiletargeting:DeleteEmailChannel", + "mobiletargeting:DeleteGcmChannel", + "mobiletargeting:DeleteSmsChannel" + ], + "Effect": "Allow", + "Resource": [ + "arn:aws:mobiletargeting:*:*:apps/*/channels/*" + ] + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/service-role/", + "PolicyId": "ANPAIUHPQXBDZUWOP3PSK", + "PolicyName": "AWSMobileHub_ServiceUseOnly", + "UpdateDate": "2017-06-02T23:35:49+00:00", + "VersionId": "v23" + }, + "AWSOpsWorksCMInstanceProfileRole": { + "Arn": "arn:aws:iam::aws:policy/AWSOpsWorksCMInstanceProfileRole", + "AttachmentCount": 0, + "CreateDate": "2016-11-24T09:48:22+00:00", + "DefaultVersionId": "v1", + "Document": { + "Statement": [ + { + "Action": [ + "s3:AbortMultipartUpload", + "s3:DeleteObject", + "s3:GetObject", + "s3:ListAllMyBuckets", + "s3:ListBucket", + "s3:ListMultipartUploadParts", + "s3:PutObject" + ], + "Effect": "Allow", + "Resource": "arn:aws:s3:::aws-opsworks-cm-*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAICSU3OSHCURP2WIZW", + "PolicyName": "AWSOpsWorksCMInstanceProfileRole", + "UpdateDate": "2016-11-24T09:48:22+00:00", + "VersionId": "v1" + }, + "AWSOpsWorksCMServiceRole": { + "Arn": "arn:aws:iam::aws:policy/service-role/AWSOpsWorksCMServiceRole", + "AttachmentCount": 0, + "CreateDate": "2017-04-03T12:00:07+00:00", + "DefaultVersionId": "v6", + "Document": { + "Statement": [ + { + "Action": [ + "s3:CreateBucket", + "s3:DeleteObject", + "s3:DeleteBucket", + "s3:GetObject", + "s3:HeadBucket", + "s3:ListBucket", + "s3:ListObjects", + "s3:PutBucketPolicy" + ], + "Effect": "Allow", + "Resource": [ + "arn:aws:s3:::aws-opsworks-cm-*" + ] + }, + { + "Action": [ + "ssm:DescribeInstanceInformation", + "ssm:GetCommandInvocation", + "ssm:ListCommandInvocations", + "ssm:ListCommands" + ], + "Effect": "Allow", + "Resource": [ + "*" + ] + }, + { + "Action": [ + "ssm:SendCommand" + ], + "Condition": { + "StringLike": { + "ssm:resourceTag/aws:cloudformation:stack-name": "aws-opsworks-cm-*" + } + }, + "Effect": "Allow", + "Resource": [ + "*" + ] + }, + { + "Action": [ + "ssm:SendCommand" + ], + "Effect": "Allow", + "Resource": [ + "arn:aws:ssm:*::document/*", + "arn:aws:s3:::aws-opsworks-cm-*" + ] + }, + { + "Action": [ + "ec2:AllocateAddress", + "ec2:AssociateAddress", + "ec2:AuthorizeSecurityGroupIngress", + "ec2:CreateImage", + "ec2:CreateSecurityGroup", + "ec2:CreateSnapshot", + "ec2:CreateTags", + "ec2:DeleteSecurityGroup", + "ec2:DeleteSnapshot", + "ec2:DeregisterImage", + "ec2:DescribeAccountAttributes", + "ec2:DescribeAddresses", + "ec2:DescribeImages", + "ec2:DescribeInstanceStatus", + "ec2:DescribeInstances", + "ec2:DescribeSecurityGroups", + "ec2:DescribeSnapshots", + "ec2:DescribeSubnets", + "ec2:DisassociateAddress", + "ec2:ReleaseAddress", + "ec2:RunInstances", + "ec2:StopInstances" + ], + "Effect": "Allow", + "Resource": [ + "*" + ] + }, + { + "Action": [ + "ec2:TerminateInstances" + ], + "Condition": { + "StringLike": { + "ec2:ResourceTag/aws:cloudformation:stack-name": "aws-opsworks-cm-*" + } + }, + "Effect": "Allow", + "Resource": [ + "*" + ] + }, + { + "Action": [ + "cloudformation:CreateStack", + "cloudformation:DeleteStack", + "cloudformation:DescribeStackEvents", + "cloudformation:DescribeStackResources", + "cloudformation:DescribeStacks", + "cloudformation:UpdateStack" + ], + "Effect": "Allow", + "Resource": [ + "arn:aws:cloudformation:*:*:stack/aws-opsworks-cm-*" + ] + }, + { + "Action": [ + "iam:PassRole" + ], + "Effect": "Allow", + "Resource": [ + "arn:aws:iam::*:role/aws-opsworks-cm-*", + "arn:aws:iam::*:role/service-role/aws-opsworks-cm-*" + ] + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/service-role/", + "PolicyId": "ANPAJ6I6MPGJE62URSHCO", + "PolicyName": "AWSOpsWorksCMServiceRole", + "UpdateDate": "2017-04-03T12:00:07+00:00", + "VersionId": "v6" + }, + "AWSOpsWorksCloudWatchLogs": { + "Arn": "arn:aws:iam::aws:policy/AWSOpsWorksCloudWatchLogs", + "AttachmentCount": 0, + "CreateDate": "2017-03-30T17:47:19+00:00", + "DefaultVersionId": "v1", + "Document": { + "Statement": [ + { + "Action": [ + "logs:CreateLogGroup", + "logs:CreateLogStream", + "logs:PutLogEvents", + "logs:DescribeLogStreams" + ], + "Effect": "Allow", + "Resource": [ + "arn:aws:logs:*:*:*" + ] + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAJXFIK7WABAY5CPXM4", + "PolicyName": "AWSOpsWorksCloudWatchLogs", + "UpdateDate": "2017-03-30T17:47:19+00:00", + "VersionId": "v1" + }, + "AWSOpsWorksFullAccess": { + "Arn": "arn:aws:iam::aws:policy/AWSOpsWorksFullAccess", + "AttachmentCount": 0, + "CreateDate": "2015-02-06T18:40:48+00:00", + "DefaultVersionId": "v1", + "Document": { + "Statement": [ + { + "Action": [ + "opsworks:*", + "ec2:DescribeAvailabilityZones", + "ec2:DescribeKeyPairs", + "ec2:DescribeSecurityGroups", + "ec2:DescribeAccountAttributes", + "ec2:DescribeAvailabilityZones", + "ec2:DescribeSecurityGroups", + "ec2:DescribeSubnets", + "ec2:DescribeVpcs", + "elasticloadbalancing:DescribeInstanceHealth", + "elasticloadbalancing:DescribeLoadBalancers", + "iam:GetRolePolicy", + "iam:ListInstanceProfiles", + "iam:ListRoles", + "iam:ListUsers", + "iam:PassRole" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAICN26VXMXASXKOQCG", + "PolicyName": "AWSOpsWorksFullAccess", + "UpdateDate": "2015-02-06T18:40:48+00:00", + "VersionId": "v1" + }, + "AWSOpsWorksInstanceRegistration": { + "Arn": "arn:aws:iam::aws:policy/AWSOpsWorksInstanceRegistration", + "AttachmentCount": 0, + "CreateDate": "2016-06-03T14:23:15+00:00", + "DefaultVersionId": "v1", + "Document": { + "Statement": [ + { + "Action": [ + "opsworks:DescribeStackProvisioningParameters", + "opsworks:DescribeStacks", + "opsworks:RegisterInstance" + ], + "Effect": "Allow", + "Resource": [ + "*" + ] + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAJG3LCPVNI4WDZCIMU", + "PolicyName": "AWSOpsWorksInstanceRegistration", + "UpdateDate": "2016-06-03T14:23:15+00:00", + "VersionId": "v1" + }, + "AWSOpsWorksRegisterCLI": { + "Arn": "arn:aws:iam::aws:policy/AWSOpsWorksRegisterCLI", + "AttachmentCount": 0, + "CreateDate": "2015-02-06T18:40:49+00:00", + "DefaultVersionId": "v1", + "Document": { + "Statement": [ + { + "Action": [ + "opsworks:AssignInstance", + "opsworks:CreateStack", + "opsworks:CreateLayer", + "opsworks:DeregisterInstance", + "opsworks:DescribeInstances", + "opsworks:DescribeStackProvisioningParameters", + "opsworks:DescribeStacks", + "opsworks:UnassignInstance" + ], + "Effect": "Allow", + "Resource": [ + "*" + ] + }, + { + "Action": [ + "ec2:DescribeInstances" + ], + "Effect": "Allow", + "Resource": [ + "*" + ] + }, + { + "Action": [ + "iam:AddUserToGroup", + "iam:CreateAccessKey", + "iam:CreateGroup", + "iam:CreateUser", + "iam:ListInstanceProfiles", + "iam:PassRole", + "iam:PutUserPolicy" + ], + "Effect": "Allow", + "Resource": [ + "*" + ] + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAJ3AB5ZBFPCQGTVDU4", + "PolicyName": "AWSOpsWorksRegisterCLI", + "UpdateDate": "2015-02-06T18:40:49+00:00", + "VersionId": "v1" + }, + "AWSOpsWorksRole": { + "Arn": "arn:aws:iam::aws:policy/service-role/AWSOpsWorksRole", + "AttachmentCount": 0, + "CreateDate": "2015-02-06T18:41:27+00:00", + "DefaultVersionId": "v1", + "Document": { + "Statement": [ + { + "Action": [ + "cloudwatch:GetMetricStatistics", + "ec2:DescribeAccountAttributes", + "ec2:DescribeAvailabilityZones", + "ec2:DescribeInstances", + "ec2:DescribeKeyPairs", + "ec2:DescribeSecurityGroups", + "ec2:DescribeSubnets", + "ec2:DescribeVpcs", + "elasticloadbalancing:DescribeInstanceHealth", + "elasticloadbalancing:DescribeLoadBalancers", + "iam:GetRolePolicy", + "iam:ListInstanceProfiles", + "iam:ListRoles", + "iam:ListUsers", + "iam:PassRole", + "opsworks:*", + "rds:*" + ], + "Effect": "Allow", + "Resource": [ + "*" + ] + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/service-role/", + "PolicyId": "ANPAIDUTMOKHJFAPJV45W", + "PolicyName": "AWSOpsWorksRole", + "UpdateDate": "2015-02-06T18:41:27+00:00", + "VersionId": "v1" + }, + "AWSQuickSightDescribeRDS": { + "Arn": "arn:aws:iam::aws:policy/service-role/AWSQuickSightDescribeRDS", + "AttachmentCount": 0, + "CreateDate": "2015-11-10T23:24:50+00:00", + "DefaultVersionId": "v1", + "Document": { + "Statement": [ + { + "Action": [ + "rds:Describe*" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/service-role/", + "PolicyId": "ANPAJU5J6OAMCJD3OO76O", + "PolicyName": "AWSQuickSightDescribeRDS", + "UpdateDate": "2015-11-10T23:24:50+00:00", + "VersionId": "v1" + }, + "AWSQuickSightDescribeRedshift": { + "Arn": "arn:aws:iam::aws:policy/service-role/AWSQuickSightDescribeRedshift", + "AttachmentCount": 0, + "CreateDate": "2015-11-10T23:25:01+00:00", + "DefaultVersionId": "v1", + "Document": { + "Statement": [ + { + "Action": [ + "redshift:Describe*" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/service-role/", + "PolicyId": "ANPAJFEM6MLSLTW4ZNBW2", + "PolicyName": "AWSQuickSightDescribeRedshift", + "UpdateDate": "2015-11-10T23:25:01+00:00", + "VersionId": "v1" + }, + "AWSQuickSightListIAM": { + "Arn": "arn:aws:iam::aws:policy/service-role/AWSQuickSightListIAM", + "AttachmentCount": 0, + "CreateDate": "2015-11-10T23:25:07+00:00", + "DefaultVersionId": "v1", + "Document": { + "Statement": [ + { + "Action": [ + "iam:List*" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/service-role/", + "PolicyId": "ANPAI3CH5UUWZN4EKGILO", + "PolicyName": "AWSQuickSightListIAM", + "UpdateDate": "2015-11-10T23:25:07+00:00", + "VersionId": "v1" + }, + "AWSQuicksightAthenaAccess": { + "Arn": "arn:aws:iam::aws:policy/service-role/AWSQuicksightAthenaAccess", + "AttachmentCount": 0, + "CreateDate": "2017-08-11T23:37:32+00:00", + "DefaultVersionId": "v3", + "Document": { + "Statement": [ + { + "Action": [ + "athena:BatchGetQueryExecution", + "athena:CancelQueryExecution", + "athena:GetCatalogs", + "athena:GetExecutionEngine", + "athena:GetExecutionEngines", + "athena:GetNamespace", + "athena:GetNamespaces", + "athena:GetQueryExecution", + "athena:GetQueryExecutions", + "athena:GetQueryResults", + "athena:GetTable", + "athena:GetTables", + "athena:ListQueryExecutions", + "athena:RunQuery", + "athena:StartQueryExecution", + "athena:StopQueryExecution" + ], + "Effect": "Allow", + "Resource": [ + "*" + ] + }, + { + "Action": [ + "glue:CreateDatabase", + "glue:DeleteDatabase", + "glue:GetDatabase", + "glue:GetDatabases", + "glue:UpdateDatabase", + "glue:CreateTable", + "glue:DeleteTable", + "glue:BatchDeleteTable", + "glue:UpdateTable", + "glue:GetTable", + "glue:GetTables", + "glue:BatchCreatePartition", + "glue:CreatePartition", + "glue:DeletePartition", + "glue:BatchDeletePartition", + "glue:UpdatePartition", + "glue:GetPartition", + "glue:GetPartitions", + "glue:BatchGetPartition" + ], + "Effect": "Allow", + "Resource": [ + "*" + ] + }, + { + "Action": [ + "s3:GetBucketLocation", + "s3:GetObject", + "s3:ListBucket", + "s3:ListBucketMultipartUploads", + "s3:ListMultipartUploadParts", + "s3:AbortMultipartUpload", + "s3:CreateBucket", + "s3:PutObject" + ], + "Effect": "Allow", + "Resource": [ + "arn:aws:s3:::aws-athena-query-results-*" + ] + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/service-role/", + "PolicyId": "ANPAI4JB77JXFQXDWNRPM", + "PolicyName": "AWSQuicksightAthenaAccess", + "UpdateDate": "2017-08-11T23:37:32+00:00", + "VersionId": "v3" + }, + "AWSStepFunctionsConsoleFullAccess": { + "Arn": "arn:aws:iam::aws:policy/AWSStepFunctionsConsoleFullAccess", + "AttachmentCount": 0, + "CreateDate": "2017-01-12T00:19:34+00:00", + "DefaultVersionId": "v2", + "Document": { + "Statement": [ + { + "Action": "states:*", + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": "iam:ListRoles", + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": "iam:PassRole", + "Effect": "Allow", + "Resource": "arn:aws:iam::*:role/service-role/StatesExecutionRole*" + }, + { + "Action": "lambda:ListFunctions", + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAJIYC52YWRX6OSMJWK", + "PolicyName": "AWSStepFunctionsConsoleFullAccess", + "UpdateDate": "2017-01-12T00:19:34+00:00", + "VersionId": "v2" + }, + "AWSStepFunctionsFullAccess": { + "Arn": "arn:aws:iam::aws:policy/AWSStepFunctionsFullAccess", + "AttachmentCount": 0, + "CreateDate": "2017-01-11T21:51:32+00:00", + "DefaultVersionId": "v1", + "Document": { + "Statement": [ + { + "Action": "states:*", + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAJXKA6VP3UFBVHDPPA", + "PolicyName": "AWSStepFunctionsFullAccess", + "UpdateDate": "2017-01-11T21:51:32+00:00", + "VersionId": "v1" + }, + "AWSStepFunctionsReadOnlyAccess": { + "Arn": "arn:aws:iam::aws:policy/AWSStepFunctionsReadOnlyAccess", + "AttachmentCount": 0, + "CreateDate": "2017-01-11T21:46:19+00:00", + "DefaultVersionId": "v1", + "Document": { + "Statement": [ + { + "Action": [ + "states:ListStateMachines", + "states:ListActivities", + "states:DescribeStateMachine", + "states:ListExecutions", + "states:DescribeExecution", + "states:GetExecutionHistory", + "states:DescribeActivity" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAJONHB2TJQDJPFW5TM", + "PolicyName": "AWSStepFunctionsReadOnlyAccess", + "UpdateDate": "2017-01-11T21:46:19+00:00", + "VersionId": "v1" + }, + "AWSStorageGatewayFullAccess": { + "Arn": "arn:aws:iam::aws:policy/AWSStorageGatewayFullAccess", + "AttachmentCount": 0, + "CreateDate": "2015-02-06T18:41:09+00:00", + "DefaultVersionId": "v1", + "Document": { + "Statement": [ + { + "Action": [ + "storagegateway:*" + ], + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": [ + "ec2:DescribeSnapshots", + "ec2:DeleteSnapshot" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAJG5SSPAVOGK3SIDGU", + "PolicyName": "AWSStorageGatewayFullAccess", + "UpdateDate": "2015-02-06T18:41:09+00:00", + "VersionId": "v1" + }, + "AWSStorageGatewayReadOnlyAccess": { + "Arn": "arn:aws:iam::aws:policy/AWSStorageGatewayReadOnlyAccess", + "AttachmentCount": 0, + "CreateDate": "2015-02-06T18:41:10+00:00", + "DefaultVersionId": "v1", + "Document": { + "Statement": [ + { + "Action": [ + "storagegateway:List*", + "storagegateway:Describe*" + ], + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": [ + "ec2:DescribeSnapshots" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAIFKCTUVOPD5NICXJK", + "PolicyName": "AWSStorageGatewayReadOnlyAccess", + "UpdateDate": "2015-02-06T18:41:10+00:00", + "VersionId": "v1" + }, + "AWSSupportAccess": { + "Arn": "arn:aws:iam::aws:policy/AWSSupportAccess", + "AttachmentCount": 0, + "CreateDate": "2015-02-06T18:41:11+00:00", + "DefaultVersionId": "v1", + "Document": { + "Statement": [ + { + "Action": [ + "support:*" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAJSNKQX2OW67GF4S7E", + "PolicyName": "AWSSupportAccess", + "UpdateDate": "2015-02-06T18:41:11+00:00", + "VersionId": "v1" + }, + "AWSWAFFullAccess": { + "Arn": "arn:aws:iam::aws:policy/AWSWAFFullAccess", + "AttachmentCount": 0, + "CreateDate": "2016-12-07T21:33:25+00:00", + "DefaultVersionId": "v2", + "Document": { + "Statement": [ + { + "Action": [ + "waf:*", + "waf-regional:*", + "elasticloadbalancing:SetWebACL" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAJMIKIAFXZEGOLRH7C", + "PolicyName": "AWSWAFFullAccess", + "UpdateDate": "2016-12-07T21:33:25+00:00", + "VersionId": "v2" + }, + "AWSWAFReadOnlyAccess": { + "Arn": "arn:aws:iam::aws:policy/AWSWAFReadOnlyAccess", + "AttachmentCount": 0, + "CreateDate": "2016-12-07T21:30:54+00:00", + "DefaultVersionId": "v2", + "Document": { + "Statement": [ + { + "Action": [ + "waf:Get*", + "waf:List*", + "waf-regional:Get*", + "waf-regional:List*" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAINZVDMX2SBF7EU2OC", + "PolicyName": "AWSWAFReadOnlyAccess", + "UpdateDate": "2016-12-07T21:30:54+00:00", + "VersionId": "v2" + }, + "AWSXrayFullAccess": { + "Arn": "arn:aws:iam::aws:policy/AWSXrayFullAccess", + "AttachmentCount": 0, + "CreateDate": "2016-12-01T18:30:55+00:00", + "DefaultVersionId": "v1", + "Document": { + "Statement": [ + { + "Action": [ + "xray:*" + ], + "Effect": "Allow", + "Resource": [ + "*" + ] + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAJQBYG45NSJMVQDB2K", + "PolicyName": "AWSXrayFullAccess", + "UpdateDate": "2016-12-01T18:30:55+00:00", + "VersionId": "v1" + }, + "AWSXrayReadOnlyAccess": { + "Arn": "arn:aws:iam::aws:policy/AWSXrayReadOnlyAccess", + "AttachmentCount": 0, + "CreateDate": "2016-12-01T18:27:02+00:00", + "DefaultVersionId": "v1", + "Document": { + "Statement": [ + { + "Action": [ + "xray:BatchGetTraces", + "xray:GetServiceGraph", + "xray:GetTraceGraph", + "xray:GetTraceSummaries" + ], + "Effect": "Allow", + "Resource": [ + "*" + ] + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAIH4OFXWPS6ZX6OPGQ", + "PolicyName": "AWSXrayReadOnlyAccess", + "UpdateDate": "2016-12-01T18:27:02+00:00", + "VersionId": "v1" + }, + "AWSXrayWriteOnlyAccess": { + "Arn": "arn:aws:iam::aws:policy/AWSXrayWriteOnlyAccess", + "AttachmentCount": 0, + "CreateDate": "2016-12-01T18:19:53+00:00", + "DefaultVersionId": "v1", + "Document": { + "Statement": [ + { + "Action": [ + "xray:PutTraceSegments", + "xray:PutTelemetryRecords" + ], + "Effect": "Allow", + "Resource": [ + "*" + ] + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAIAACM4LMYSRGBCTM6", + "PolicyName": "AWSXrayWriteOnlyAccess", + "UpdateDate": "2016-12-01T18:19:53+00:00", + "VersionId": "v1" + }, + "AdministratorAccess": { + "Arn": "arn:aws:iam::aws:policy/AdministratorAccess", + "AttachmentCount": 3, + "CreateDate": "2015-02-06T18:39:46+00:00", + "DefaultVersionId": "v1", + "Document": { + "Statement": [ + { + "Action": "*", + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAIWMBCKSKIEE64ZLYK", + "PolicyName": "AdministratorAccess", + "UpdateDate": "2015-02-06T18:39:46+00:00", + "VersionId": "v1" + }, + "AmazonAPIGatewayAdministrator": { + "Arn": "arn:aws:iam::aws:policy/AmazonAPIGatewayAdministrator", + "AttachmentCount": 0, + "CreateDate": "2015-07-09T17:34:45+00:00", + "DefaultVersionId": "v1", + "Document": { + "Statement": [ + { + "Action": [ + "apigateway:*" + ], + "Effect": "Allow", + "Resource": "arn:aws:apigateway:*::/*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAJ4PT6VY5NLKTNUYSI", + "PolicyName": "AmazonAPIGatewayAdministrator", + "UpdateDate": "2015-07-09T17:34:45+00:00", + "VersionId": "v1" + }, + "AmazonAPIGatewayInvokeFullAccess": { + "Arn": "arn:aws:iam::aws:policy/AmazonAPIGatewayInvokeFullAccess", + "AttachmentCount": 0, + "CreateDate": "2015-07-09T17:36:12+00:00", + "DefaultVersionId": "v1", + "Document": { + "Statement": [ + { + "Action": [ + "execute-api:Invoke" + ], + "Effect": "Allow", + "Resource": "arn:aws:execute-api:*:*:*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAIIWAX2NOOQJ4AIEQ6", + "PolicyName": "AmazonAPIGatewayInvokeFullAccess", + "UpdateDate": "2015-07-09T17:36:12+00:00", + "VersionId": "v1" + }, + "AmazonAPIGatewayPushToCloudWatchLogs": { + "Arn": "arn:aws:iam::aws:policy/service-role/AmazonAPIGatewayPushToCloudWatchLogs", + "AttachmentCount": 0, + "CreateDate": "2015-11-11T23:41:46+00:00", + "DefaultVersionId": "v1", + "Document": { + "Statement": [ + { + "Action": [ + "logs:CreateLogGroup", + "logs:CreateLogStream", + "logs:DescribeLogGroups", + "logs:DescribeLogStreams", + "logs:PutLogEvents", + "logs:GetLogEvents", + "logs:FilterLogEvents" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/service-role/", + "PolicyId": "ANPAIK4GFO7HLKYN64ASK", + "PolicyName": "AmazonAPIGatewayPushToCloudWatchLogs", + "UpdateDate": "2015-11-11T23:41:46+00:00", + "VersionId": "v1" + }, + "AmazonAppStreamFullAccess": { + "Arn": "arn:aws:iam::aws:policy/AmazonAppStreamFullAccess", + "AttachmentCount": 0, + "CreateDate": "2017-09-07T23:56:23+00:00", + "DefaultVersionId": "v2", + "Document": { + "Statement": [ + { + "Action": [ + "appstream:*" + ], + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": [ + "application-autoscaling:DeleteScalingPolicy", + "application-autoscaling:DescribeScalableTargets", + "application-autoscaling:DescribeScalingPolicies", + "application-autoscaling:PutScalingPolicy", + "application-autoscaling:RegisterScalableTarget" + ], + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": [ + "cloudwatch:DeleteAlarms", + "cloudwatch:DescribeAlarms", + "cloudwatch:GetMetricStatistics", + "cloudwatch:PutMetricAlarm" + ], + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": [ + "ec2:DescribeRouteTables", + "ec2:DescribeSecurityGroups", + "ec2:DescribeSubnets", + "ec2:DescribeVpcs" + ], + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": "iam:ListRoles", + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": "iam:PassRole", + "Condition": { + "StringLike": { + "iam:PassedToService": "application-autoscaling.amazonaws.com" + } + }, + "Effect": "Allow", + "Resource": "arn:aws:iam::*:role/service-role/ApplicationAutoScalingForAmazonAppStreamAccess" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAJLZZXU2YQVGL4QDNC", + "PolicyName": "AmazonAppStreamFullAccess", + "UpdateDate": "2017-09-07T23:56:23+00:00", + "VersionId": "v2" + }, + "AmazonAppStreamReadOnlyAccess": { + "Arn": "arn:aws:iam::aws:policy/AmazonAppStreamReadOnlyAccess", + "AttachmentCount": 0, + "CreateDate": "2016-12-07T21:00:06+00:00", + "DefaultVersionId": "v2", + "Document": { + "Statement": [ + { + "Action": [ + "appstream:Get*", + "appstream:List*", + "appstream:Describe*" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAJXIFDGB4VBX23DX7K", + "PolicyName": "AmazonAppStreamReadOnlyAccess", + "UpdateDate": "2016-12-07T21:00:06+00:00", + "VersionId": "v2" + }, + "AmazonAppStreamServiceAccess": { + "Arn": "arn:aws:iam::aws:policy/service-role/AmazonAppStreamServiceAccess", + "AttachmentCount": 0, + "CreateDate": "2017-05-23T23:00:47+00:00", + "DefaultVersionId": "v3", + "Document": { + "Statement": [ + { + "Action": [ + "ec2:DescribeVpcs", + "ec2:DescribeSubnets", + "ec2:DescribeAvailabilityZones", + "ec2:CreateNetworkInterface", + "ec2:DescribeNetworkInterfaces", + "ec2:DeleteNetworkInterface", + "ec2:DescribeSubnets", + "ec2:AssociateAddress", + "ec2:DisassociateAddress", + "ec2:DescribeRouteTables", + "ec2:DescribeSecurityGroups" + ], + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": [ + "s3:CreateBucket", + "s3:ListBucket", + "s3:GetObject", + "s3:PutObject", + "s3:DeleteObject", + "s3:GetObjectVersion", + "s3:DeleteObjectVersion", + "s3:PutBucketPolicy" + ], + "Effect": "Allow", + "Resource": "arn:aws:s3:::appstream2-36fb080bb8-*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/service-role/", + "PolicyId": "ANPAISBRZ7LMMCBYEF3SE", + "PolicyName": "AmazonAppStreamServiceAccess", + "UpdateDate": "2017-05-23T23:00:47+00:00", + "VersionId": "v3" + }, + "AmazonAthenaFullAccess": { + "Arn": "arn:aws:iam::aws:policy/AmazonAthenaFullAccess", + "AttachmentCount": 0, + "CreateDate": "2017-09-13T00:13:48+00:00", + "DefaultVersionId": "v3", + "Document": { + "Statement": [ + { + "Action": [ + "athena:*" + ], + "Effect": "Allow", + "Resource": [ + "*" + ] + }, + { + "Action": [ + "glue:CreateDatabase", + "glue:DeleteDatabase", + "glue:GetDatabase", + "glue:GetDatabases", + "glue:UpdateDatabase", + "glue:CreateTable", + "glue:DeleteTable", + "glue:BatchDeleteTable", + "glue:UpdateTable", + "glue:GetTable", + "glue:GetTables", + "glue:BatchCreatePartition", + "glue:CreatePartition", + "glue:DeletePartition", + "glue:BatchDeletePartition", + "glue:UpdatePartition", + "glue:GetPartition", + "glue:GetPartitions", + "glue:BatchGetPartition" + ], + "Effect": "Allow", + "Resource": [ + "*" + ] + }, + { + "Action": [ + "s3:GetBucketLocation", + "s3:GetObject", + "s3:ListBucket", + "s3:ListBucketMultipartUploads", + "s3:ListMultipartUploadParts", + "s3:AbortMultipartUpload", + "s3:CreateBucket", + "s3:PutObject" + ], + "Effect": "Allow", + "Resource": [ + "arn:aws:s3:::aws-athena-query-results-*" + ] + }, + { + "Action": [ + "s3:GetObject" + ], + "Effect": "Allow", + "Resource": [ + "arn:aws:s3:::athena-examples*" + ] + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAIPJMLMD4C7RYZ6XCK", + "PolicyName": "AmazonAthenaFullAccess", + "UpdateDate": "2017-09-13T00:13:48+00:00", + "VersionId": "v3" + }, + "AmazonCloudDirectoryFullAccess": { + "Arn": "arn:aws:iam::aws:policy/AmazonCloudDirectoryFullAccess", + "AttachmentCount": 0, + "CreateDate": "2017-02-25T00:41:39+00:00", + "DefaultVersionId": "v1", + "Document": { + "Statement": [ + { + "Action": [ + "clouddirectory:*" + ], + "Effect": "Allow", + "Resource": [ + "*" + ] + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAJG3XQK77ATFLCF2CK", + "PolicyName": "AmazonCloudDirectoryFullAccess", + "UpdateDate": "2017-02-25T00:41:39+00:00", + "VersionId": "v1" + }, + "AmazonCloudDirectoryReadOnlyAccess": { + "Arn": "arn:aws:iam::aws:policy/AmazonCloudDirectoryReadOnlyAccess", + "AttachmentCount": 0, + "CreateDate": "2017-02-28T23:42:06+00:00", + "DefaultVersionId": "v1", + "Document": { + "Statement": [ + { + "Action": [ + "clouddirectory:List*", + "clouddirectory:Get*", + "clouddirectory:LookupPolicy", + "clouddirectory:BatchRead" + ], + "Effect": "Allow", + "Resource": [ + "*" + ] + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAICMSZQGR3O62KMD6M", + "PolicyName": "AmazonCloudDirectoryReadOnlyAccess", + "UpdateDate": "2017-02-28T23:42:06+00:00", + "VersionId": "v1" + }, + "AmazonCognitoDeveloperAuthenticatedIdentities": { + "Arn": "arn:aws:iam::aws:policy/AmazonCognitoDeveloperAuthenticatedIdentities", + "AttachmentCount": 0, + "CreateDate": "2015-03-24T17:22:23+00:00", + "DefaultVersionId": "v1", + "Document": { + "Statement": [ + { + "Action": [ + "cognito-identity:GetOpenIdTokenForDeveloperIdentity", + "cognito-identity:LookupDeveloperIdentity", + "cognito-identity:MergeDeveloperIdentities", + "cognito-identity:UnlinkDeveloperIdentity" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAIQOKZ5BGKLCMTXH4W", + "PolicyName": "AmazonCognitoDeveloperAuthenticatedIdentities", + "UpdateDate": "2015-03-24T17:22:23+00:00", + "VersionId": "v1" + }, + "AmazonCognitoPowerUser": { + "Arn": "arn:aws:iam::aws:policy/AmazonCognitoPowerUser", + "AttachmentCount": 0, + "CreateDate": "2016-06-02T16:57:56+00:00", + "DefaultVersionId": "v2", + "Document": { + "Statement": [ + { + "Action": [ + "cognito-identity:*", + "cognito-idp:*", + "cognito-sync:*", + "iam:ListRoles", + "iam:ListOpenIdConnectProviders", + "sns:ListPlatformApplications" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAJKW5H2HNCPGCYGR6Y", + "PolicyName": "AmazonCognitoPowerUser", + "UpdateDate": "2016-06-02T16:57:56+00:00", + "VersionId": "v2" + }, + "AmazonCognitoReadOnly": { + "Arn": "arn:aws:iam::aws:policy/AmazonCognitoReadOnly", + "AttachmentCount": 0, + "CreateDate": "2016-06-02T17:30:24+00:00", + "DefaultVersionId": "v2", + "Document": { + "Statement": [ + { + "Action": [ + "cognito-identity:Describe*", + "cognito-identity:Get*", + "cognito-identity:List*", + "cognito-idp:Describe*", + "cognito-idp:AdminGetUser", + "cognito-idp:List*", + "cognito-sync:Describe*", + "cognito-sync:Get*", + "cognito-sync:List*", + "iam:ListOpenIdConnectProviders", + "iam:ListRoles", + "sns:ListPlatformApplications" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAJBFTRZD2GQGJHSVQK", + "PolicyName": "AmazonCognitoReadOnly", + "UpdateDate": "2016-06-02T17:30:24+00:00", + "VersionId": "v2" + }, + "AmazonDMSCloudWatchLogsRole": { + "Arn": "arn:aws:iam::aws:policy/service-role/AmazonDMSCloudWatchLogsRole", + "AttachmentCount": 0, + "CreateDate": "2016-01-07T23:44:53+00:00", + "DefaultVersionId": "v1", + "Document": { + "Statement": [ + { + "Action": [ + "logs:DescribeLogGroups" + ], + "Effect": "Allow", + "Resource": [ + "*" + ], + "Sid": "AllowDescribeOnAllLogGroups" + }, + { + "Action": [ + "logs:DescribeLogStreams" + ], + "Effect": "Allow", + "Resource": [ + "arn:aws:logs:*:*:log-group:dms-tasks-*" + ], + "Sid": "AllowDescribeOfAllLogStreamsOnDmsTasksLogGroup" + }, + { + "Action": [ + "logs:CreateLogGroup" + ], + "Effect": "Allow", + "Resource": [ + "arn:aws:logs:*:*:log-group:dms-tasks-*" + ], + "Sid": "AllowCreationOfDmsTasksLogGroups" + }, + { + "Action": [ + "logs:CreateLogStream" + ], + "Effect": "Allow", + "Resource": [ + "arn:aws:logs:*:*:log-group:dms-tasks-*:log-stream:dms-task-*" + ], + "Sid": "AllowCreationOfDmsTaskLogStream" + }, + { + "Action": [ + "logs:PutLogEvents" + ], + "Effect": "Allow", + "Resource": [ + "arn:aws:logs:*:*:log-group:dms-tasks-*:log-stream:dms-task-*" + ], + "Sid": "AllowUploadOfLogEventsToDmsTaskLogStream" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/service-role/", + "PolicyId": "ANPAJBG7UXZZXUJD3TDJE", + "PolicyName": "AmazonDMSCloudWatchLogsRole", + "UpdateDate": "2016-01-07T23:44:53+00:00", + "VersionId": "v1" + }, + "AmazonDMSRedshiftS3Role": { + "Arn": "arn:aws:iam::aws:policy/service-role/AmazonDMSRedshiftS3Role", + "AttachmentCount": 0, + "CreateDate": "2016-04-20T17:05:56+00:00", + "DefaultVersionId": "v1", + "Document": { + "Statement": [ + { + "Action": [ + "s3:CreateBucket", + "s3:ListBucket", + "s3:DeleteBucket", + "s3:GetBucketLocation", + "s3:GetObject", + "s3:PutObject", + "s3:DeleteObject", + "s3:GetObjectVersion", + "s3:GetBucketPolicy", + "s3:PutBucketPolicy", + "s3:DeleteBucketPolicy" + ], + "Effect": "Allow", + "Resource": "arn:aws:s3:::dms-*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/service-role/", + "PolicyId": "ANPAI3CCUQ4U5WNC5F6B6", + "PolicyName": "AmazonDMSRedshiftS3Role", + "UpdateDate": "2016-04-20T17:05:56+00:00", + "VersionId": "v1" + }, + "AmazonDMSVPCManagementRole": { + "Arn": "arn:aws:iam::aws:policy/service-role/AmazonDMSVPCManagementRole", + "AttachmentCount": 0, + "CreateDate": "2016-05-23T16:29:57+00:00", + "DefaultVersionId": "v3", + "Document": { + "Statement": [ + { + "Action": [ + "ec2:CreateNetworkInterface", + "ec2:DescribeAvailabilityZones", + "ec2:DescribeInternetGateways", + "ec2:DescribeSecurityGroups", + "ec2:DescribeSubnets", + "ec2:DescribeVpcs", + "ec2:DeleteNetworkInterface", + "ec2:ModifyNetworkInterfaceAttribute" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/service-role/", + "PolicyId": "ANPAJHKIGMBQI4AEFFSYO", + "PolicyName": "AmazonDMSVPCManagementRole", + "UpdateDate": "2016-05-23T16:29:57+00:00", + "VersionId": "v3" + }, + "AmazonDRSVPCManagement": { + "Arn": "arn:aws:iam::aws:policy/AmazonDRSVPCManagement", + "AttachmentCount": 0, + "CreateDate": "2015-09-02T00:09:20+00:00", + "DefaultVersionId": "v1", + "Document": { + "Statement": [ + { + "Action": [ + "ec2:AuthorizeSecurityGroupIngress", + "ec2:CreateNetworkInterface", + "ec2:CreateSecurityGroup", + "ec2:DescribeAvailabilityZones", + "ec2:DescribeInternetGateways", + "ec2:DescribeSecurityGroups", + "ec2:DescribeSubnets", + "ec2:DescribeVpcAttribute", + "ec2:DescribeVpcs", + "ec2:DeleteNetworkInterface", + "ec2:DeleteSecurityGroup", + "ec2:ModifyNetworkInterfaceAttribute", + "ec2:RevokeSecurityGroupIngress" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAJPXIBTTZMBEFEX6UA", + "PolicyName": "AmazonDRSVPCManagement", + "UpdateDate": "2015-09-02T00:09:20+00:00", + "VersionId": "v1" + }, + "AmazonDynamoDBFullAccess": { + "Arn": "arn:aws:iam::aws:policy/AmazonDynamoDBFullAccess", + "AttachmentCount": 0, + "CreateDate": "2017-06-28T23:23:34+00:00", + "DefaultVersionId": "v5", + "Document": { + "Statement": [ + { + "Action": [ + "dynamodb:*", + "dax:*", + "application-autoscaling:DeleteScalingPolicy", + "application-autoscaling:DeregisterScalableTarget", + "application-autoscaling:DescribeScalableTargets", + "application-autoscaling:DescribeScalingActivities", + "application-autoscaling:DescribeScalingPolicies", + "application-autoscaling:PutScalingPolicy", + "application-autoscaling:RegisterScalableTarget", + "cloudwatch:DeleteAlarms", + "cloudwatch:DescribeAlarmHistory", + "cloudwatch:DescribeAlarms", + "cloudwatch:DescribeAlarmsForMetric", + "cloudwatch:GetMetricStatistics", + "cloudwatch:ListMetrics", + "cloudwatch:PutMetricAlarm", + "datapipeline:ActivatePipeline", + "datapipeline:CreatePipeline", + "datapipeline:DeletePipeline", + "datapipeline:DescribeObjects", + "datapipeline:DescribePipelines", + "datapipeline:GetPipelineDefinition", + "datapipeline:ListPipelines", + "datapipeline:PutPipelineDefinition", + "datapipeline:QueryObjects", + "ec2:DescribeVpcs", + "ec2:DescribeSubnets", + "ec2:DescribeSecurityGroups", + "iam:GetRole", + "iam:ListRoles", + "sns:CreateTopic", + "sns:DeleteTopic", + "sns:ListSubscriptions", + "sns:ListSubscriptionsByTopic", + "sns:ListTopics", + "sns:Subscribe", + "sns:Unsubscribe", + "sns:SetTopicAttributes", + "lambda:CreateFunction", + "lambda:ListFunctions", + "lambda:ListEventSourceMappings", + "lambda:CreateEventSourceMapping", + "lambda:DeleteEventSourceMapping", + "lambda:GetFunctionConfiguration", + "lambda:DeleteFunction" + ], + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": [ + "iam:PassRole" + ], + "Condition": { + "StringLike": { + "iam:PassedToService": [ + "application-autoscaling.amazonaws.com", + "dax.amazonaws.com" + ] + } + }, + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAINUGF2JSOSUY76KYA", + "PolicyName": "AmazonDynamoDBFullAccess", + "UpdateDate": "2017-06-28T23:23:34+00:00", + "VersionId": "v5" + }, + "AmazonDynamoDBFullAccesswithDataPipeline": { + "Arn": "arn:aws:iam::aws:policy/AmazonDynamoDBFullAccesswithDataPipeline", + "AttachmentCount": 0, + "CreateDate": "2015-11-12T02:17:42+00:00", + "DefaultVersionId": "v2", + "Document": { + "Statement": [ + { + "Action": [ + "cloudwatch:DeleteAlarms", + "cloudwatch:DescribeAlarmHistory", + "cloudwatch:DescribeAlarms", + "cloudwatch:DescribeAlarmsForMetric", + "cloudwatch:GetMetricStatistics", + "cloudwatch:ListMetrics", + "cloudwatch:PutMetricAlarm", + "dynamodb:*", + "sns:CreateTopic", + "sns:DeleteTopic", + "sns:ListSubscriptions", + "sns:ListSubscriptionsByTopic", + "sns:ListTopics", + "sns:Subscribe", + "sns:Unsubscribe", + "sns:SetTopicAttributes" + ], + "Effect": "Allow", + "Resource": "*", + "Sid": "DDBConsole" + }, + { + "Action": [ + "lambda:*", + "iam:ListRoles" + ], + "Effect": "Allow", + "Resource": "*", + "Sid": "DDBConsoleTriggers" + }, + { + "Action": [ + "datapipeline:*", + "iam:ListRoles" + ], + "Effect": "Allow", + "Resource": "*", + "Sid": "DDBConsoleImportExport" + }, + { + "Action": [ + "iam:GetRolePolicy", + "iam:PassRole" + ], + "Effect": "Allow", + "Resource": [ + "*" + ], + "Sid": "IAMEDPRoles" + }, + { + "Action": [ + "ec2:CreateTags", + "ec2:DescribeInstances", + "ec2:RunInstances", + "ec2:StartInstances", + "ec2:StopInstances", + "ec2:TerminateInstances", + "elasticmapreduce:*", + "datapipeline:*" + ], + "Effect": "Allow", + "Resource": "*", + "Sid": "EMR" + }, + { + "Action": [ + "s3:DeleteObject", + "s3:Get*", + "s3:List*", + "s3:Put*" + ], + "Effect": "Allow", + "Resource": [ + "*" + ], + "Sid": "S3" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAJ3ORT7KDISSXGHJXA", + "PolicyName": "AmazonDynamoDBFullAccesswithDataPipeline", + "UpdateDate": "2015-11-12T02:17:42+00:00", + "VersionId": "v2" + }, + "AmazonDynamoDBReadOnlyAccess": { + "Arn": "arn:aws:iam::aws:policy/AmazonDynamoDBReadOnlyAccess", + "AttachmentCount": 0, + "CreateDate": "2017-06-12T21:11:40+00:00", + "DefaultVersionId": "v5", + "Document": { + "Statement": [ + { + "Action": [ + "application-autoscaling:DescribeScalableTargets", + "application-autoscaling:DescribeScalingActivities", + "application-autoscaling:DescribeScalingPolicies", + "cloudwatch:DescribeAlarmHistory", + "cloudwatch:DescribeAlarms", + "cloudwatch:DescribeAlarmsForMetric", + "cloudwatch:GetMetricStatistics", + "cloudwatch:ListMetrics", + "datapipeline:DescribeObjects", + "datapipeline:DescribePipelines", + "datapipeline:GetPipelineDefinition", + "datapipeline:ListPipelines", + "datapipeline:QueryObjects", + "dynamodb:BatchGetItem", + "dynamodb:DescribeTable", + "dynamodb:GetItem", + "dynamodb:ListTables", + "dynamodb:Query", + "dynamodb:Scan", + "dynamodb:DescribeReservedCapacity", + "dynamodb:DescribeReservedCapacityOfferings", + "dynamodb:ListTagsOfResource", + "dynamodb:DescribeTimeToLive", + "dynamodb:DescribeLimits", + "iam:GetRole", + "iam:ListRoles", + "sns:ListSubscriptionsByTopic", + "sns:ListTopics", + "lambda:ListFunctions", + "lambda:ListEventSourceMappings", + "lambda:GetFunctionConfiguration" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAIY2XFNA232XJ6J7X2", + "PolicyName": "AmazonDynamoDBReadOnlyAccess", + "UpdateDate": "2017-06-12T21:11:40+00:00", + "VersionId": "v5" + }, + "AmazonEC2ContainerRegistryFullAccess": { + "Arn": "arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryFullAccess", + "AttachmentCount": 0, + "CreateDate": "2015-12-21T17:06:48+00:00", + "DefaultVersionId": "v1", + "Document": { + "Statement": [ + { + "Action": [ + "ecr:*" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAIESRL7KD7IIVF6V4W", + "PolicyName": "AmazonEC2ContainerRegistryFullAccess", + "UpdateDate": "2015-12-21T17:06:48+00:00", + "VersionId": "v1" + }, + "AmazonEC2ContainerRegistryPowerUser": { + "Arn": "arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryPowerUser", + "AttachmentCount": 0, + "CreateDate": "2016-10-11T22:28:07+00:00", + "DefaultVersionId": "v2", + "Document": { + "Statement": [ + { + "Action": [ + "ecr:GetAuthorizationToken", + "ecr:BatchCheckLayerAvailability", + "ecr:GetDownloadUrlForLayer", + "ecr:GetRepositoryPolicy", + "ecr:DescribeRepositories", + "ecr:ListImages", + "ecr:DescribeImages", + "ecr:BatchGetImage", + "ecr:InitiateLayerUpload", + "ecr:UploadLayerPart", + "ecr:CompleteLayerUpload", + "ecr:PutImage" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAJDNE5PIHROIBGGDDW", + "PolicyName": "AmazonEC2ContainerRegistryPowerUser", + "UpdateDate": "2016-10-11T22:28:07+00:00", + "VersionId": "v2" + }, + "AmazonEC2ContainerRegistryReadOnly": { + "Arn": "arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryReadOnly", + "AttachmentCount": 0, + "CreateDate": "2016-10-11T22:08:43+00:00", + "DefaultVersionId": "v2", + "Document": { + "Statement": [ + { + "Action": [ + "ecr:GetAuthorizationToken", + "ecr:BatchCheckLayerAvailability", + "ecr:GetDownloadUrlForLayer", + "ecr:GetRepositoryPolicy", + "ecr:DescribeRepositories", + "ecr:ListImages", + "ecr:DescribeImages", + "ecr:BatchGetImage" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAIFYZPA37OOHVIH7KQ", + "PolicyName": "AmazonEC2ContainerRegistryReadOnly", + "UpdateDate": "2016-10-11T22:08:43+00:00", + "VersionId": "v2" + }, + "AmazonEC2ContainerServiceAutoscaleRole": { + "Arn": "arn:aws:iam::aws:policy/service-role/AmazonEC2ContainerServiceAutoscaleRole", + "AttachmentCount": 1, + "CreateDate": "2016-05-12T23:25:44+00:00", + "DefaultVersionId": "v1", + "Document": { + "Statement": [ + { + "Action": [ + "ecs:DescribeServices", + "ecs:UpdateService" + ], + "Effect": "Allow", + "Resource": [ + "*" + ] + }, + { + "Action": [ + "cloudwatch:DescribeAlarms" + ], + "Effect": "Allow", + "Resource": [ + "*" + ] + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/service-role/", + "PolicyId": "ANPAIUAP3EGGGXXCPDQKK", + "PolicyName": "AmazonEC2ContainerServiceAutoscaleRole", + "UpdateDate": "2016-05-12T23:25:44+00:00", + "VersionId": "v1" + }, + "AmazonEC2ContainerServiceEventsRole": { + "Arn": "arn:aws:iam::aws:policy/service-role/AmazonEC2ContainerServiceEventsRole", + "AttachmentCount": 0, + "CreateDate": "2017-05-30T16:51:35+00:00", + "DefaultVersionId": "v1", + "Document": { + "Statement": [ + { + "Action": [ + "ecs:RunTask" + ], + "Effect": "Allow", + "Resource": [ + "*" + ] + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/service-role/", + "PolicyId": "ANPAITKFNIUAG27VSYNZ4", + "PolicyName": "AmazonEC2ContainerServiceEventsRole", + "UpdateDate": "2017-05-30T16:51:35+00:00", + "VersionId": "v1" + }, + "AmazonEC2ContainerServiceFullAccess": { + "Arn": "arn:aws:iam::aws:policy/AmazonEC2ContainerServiceFullAccess", + "AttachmentCount": 0, + "CreateDate": "2017-06-08T00:18:56+00:00", + "DefaultVersionId": "v4", + "Document": { + "Statement": [ + { + "Action": [ + "autoscaling:Describe*", + "autoscaling:UpdateAutoScalingGroup", + "cloudformation:CreateStack", + "cloudformation:DeleteStack", + "cloudformation:DescribeStack*", + "cloudformation:UpdateStack", + "cloudwatch:GetMetricStatistics", + "ec2:Describe*", + "elasticloadbalancing:*", + "ecs:*", + "events:DescribeRule", + "events:DeleteRule", + "events:ListRuleNamesByTarget", + "events:ListTargetsByRule", + "events:PutRule", + "events:PutTargets", + "events:RemoveTargets", + "iam:ListInstanceProfiles", + "iam:ListRoles", + "iam:PassRole" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAJALOYVTPDZEMIACSM", + "PolicyName": "AmazonEC2ContainerServiceFullAccess", + "UpdateDate": "2017-06-08T00:18:56+00:00", + "VersionId": "v4" + }, + "AmazonEC2ContainerServiceRole": { + "Arn": "arn:aws:iam::aws:policy/service-role/AmazonEC2ContainerServiceRole", + "AttachmentCount": 1, + "CreateDate": "2016-08-11T13:08:01+00:00", + "DefaultVersionId": "v2", + "Document": { + "Statement": [ + { + "Action": [ + "ec2:AuthorizeSecurityGroupIngress", + "ec2:Describe*", + "elasticloadbalancing:DeregisterInstancesFromLoadBalancer", + "elasticloadbalancing:DeregisterTargets", + "elasticloadbalancing:Describe*", + "elasticloadbalancing:RegisterInstancesWithLoadBalancer", + "elasticloadbalancing:RegisterTargets" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/service-role/", + "PolicyId": "ANPAJO53W2XHNACG7V77Q", + "PolicyName": "AmazonEC2ContainerServiceRole", + "UpdateDate": "2016-08-11T13:08:01+00:00", + "VersionId": "v2" + }, + "AmazonEC2ContainerServiceforEC2Role": { + "Arn": "arn:aws:iam::aws:policy/service-role/AmazonEC2ContainerServiceforEC2Role", + "AttachmentCount": 1, + "CreateDate": "2017-05-17T23:09:13+00:00", + "DefaultVersionId": "v5", + "Document": { + "Statement": [ + { + "Action": [ + "ecs:CreateCluster", + "ecs:DeregisterContainerInstance", + "ecs:DiscoverPollEndpoint", + "ecs:Poll", + "ecs:RegisterContainerInstance", + "ecs:StartTelemetrySession", + "ecs:UpdateContainerInstancesState", + "ecs:Submit*", + "ecr:GetAuthorizationToken", + "ecr:BatchCheckLayerAvailability", + "ecr:GetDownloadUrlForLayer", + "ecr:BatchGetImage", + "logs:CreateLogStream", + "logs:PutLogEvents" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/service-role/", + "PolicyId": "ANPAJLYJCVHC7TQHCSQDS", + "PolicyName": "AmazonEC2ContainerServiceforEC2Role", + "UpdateDate": "2017-05-17T23:09:13+00:00", + "VersionId": "v5" + }, + "AmazonEC2FullAccess": { + "Arn": "arn:aws:iam::aws:policy/AmazonEC2FullAccess", + "AttachmentCount": 1, + "CreateDate": "2015-02-06T18:40:15+00:00", + "DefaultVersionId": "v1", + "Document": { + "Statement": [ + { + "Action": "ec2:*", + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": "elasticloadbalancing:*", + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": "cloudwatch:*", + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": "autoscaling:*", + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAI3VAJF5ZCRZ7MCQE6", + "PolicyName": "AmazonEC2FullAccess", + "UpdateDate": "2015-02-06T18:40:15+00:00", + "VersionId": "v1" + }, + "AmazonEC2ReadOnlyAccess": { + "Arn": "arn:aws:iam::aws:policy/AmazonEC2ReadOnlyAccess", + "AttachmentCount": 0, + "CreateDate": "2015-02-06T18:40:17+00:00", + "DefaultVersionId": "v1", + "Document": { + "Statement": [ + { + "Action": "ec2:Describe*", + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": "elasticloadbalancing:Describe*", + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": [ + "cloudwatch:ListMetrics", + "cloudwatch:GetMetricStatistics", + "cloudwatch:Describe*" + ], + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": "autoscaling:Describe*", + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAIGDT4SV4GSETWTBZK", + "PolicyName": "AmazonEC2ReadOnlyAccess", + "UpdateDate": "2015-02-06T18:40:17+00:00", + "VersionId": "v1" + }, + "AmazonEC2ReportsAccess": { + "Arn": "arn:aws:iam::aws:policy/AmazonEC2ReportsAccess", + "AttachmentCount": 0, + "CreateDate": "2015-02-06T18:40:16+00:00", + "DefaultVersionId": "v1", + "Document": { + "Statement": [ + { + "Action": "ec2-reports:*", + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAIU6NBZVF2PCRW36ZW", + "PolicyName": "AmazonEC2ReportsAccess", + "UpdateDate": "2015-02-06T18:40:16+00:00", + "VersionId": "v1" + }, + "AmazonEC2RoleforAWSCodeDeploy": { + "Arn": "arn:aws:iam::aws:policy/service-role/AmazonEC2RoleforAWSCodeDeploy", + "AttachmentCount": 0, + "CreateDate": "2017-03-20T17:14:10+00:00", + "DefaultVersionId": "v2", + "Document": { + "Statement": [ + { + "Action": [ + "s3:GetObject", + "s3:GetObjectVersion", + "s3:ListBucket" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/service-role/", + "PolicyId": "ANPAIAZKXZ27TAJ4PVWGK", + "PolicyName": "AmazonEC2RoleforAWSCodeDeploy", + "UpdateDate": "2017-03-20T17:14:10+00:00", + "VersionId": "v2" + }, + "AmazonEC2RoleforDataPipelineRole": { + "Arn": "arn:aws:iam::aws:policy/service-role/AmazonEC2RoleforDataPipelineRole", + "AttachmentCount": 0, + "CreateDate": "2016-02-22T17:24:05+00:00", + "DefaultVersionId": "v3", + "Document": { + "Statement": [ + { + "Action": [ + "cloudwatch:*", + "datapipeline:*", + "dynamodb:*", + "ec2:Describe*", + "elasticmapreduce:AddJobFlowSteps", + "elasticmapreduce:Describe*", + "elasticmapreduce:ListInstance*", + "elasticmapreduce:ModifyInstanceGroups", + "rds:Describe*", + "redshift:DescribeClusters", + "redshift:DescribeClusterSecurityGroups", + "s3:*", + "sdb:*", + "sns:*", + "sqs:*" + ], + "Effect": "Allow", + "Resource": [ + "*" + ] + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/service-role/", + "PolicyId": "ANPAJ3Z5I2WAJE5DN2J36", + "PolicyName": "AmazonEC2RoleforDataPipelineRole", + "UpdateDate": "2016-02-22T17:24:05+00:00", + "VersionId": "v3" + }, + "AmazonEC2RoleforSSM": { + "Arn": "arn:aws:iam::aws:policy/service-role/AmazonEC2RoleforSSM", + "AttachmentCount": 0, + "CreateDate": "2017-08-10T20:49:08+00:00", + "DefaultVersionId": "v4", + "Document": { + "Statement": [ + { + "Action": [ + "ssm:DescribeAssociation", + "ssm:GetDeployablePatchSnapshotForInstance", + "ssm:GetDocument", + "ssm:GetParameters", + "ssm:ListAssociations", + "ssm:ListInstanceAssociations", + "ssm:PutInventory", + "ssm:PutComplianceItems", + "ssm:UpdateAssociationStatus", + "ssm:UpdateInstanceAssociationStatus", + "ssm:UpdateInstanceInformation" + ], + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": [ + "ec2messages:AcknowledgeMessage", + "ec2messages:DeleteMessage", + "ec2messages:FailMessage", + "ec2messages:GetEndpoint", + "ec2messages:GetMessages", + "ec2messages:SendReply" + ], + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": [ + "cloudwatch:PutMetricData" + ], + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": [ + "ec2:DescribeInstanceStatus" + ], + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": [ + "ds:CreateComputer", + "ds:DescribeDirectories" + ], + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": [ + "logs:CreateLogGroup", + "logs:CreateLogStream", + "logs:DescribeLogGroups", + "logs:DescribeLogStreams", + "logs:PutLogEvents" + ], + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": [ + "s3:PutObject", + "s3:GetObject", + "s3:AbortMultipartUpload", + "s3:ListMultipartUploadParts", + "s3:ListBucketMultipartUploads" + ], + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": [ + "s3:ListBucket" + ], + "Effect": "Allow", + "Resource": "arn:aws:s3:::amazon-ssm-packages-*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/service-role/", + "PolicyId": "ANPAI6TL3SMY22S4KMMX6", + "PolicyName": "AmazonEC2RoleforSSM", + "UpdateDate": "2017-08-10T20:49:08+00:00", + "VersionId": "v4" + }, + "AmazonEC2SpotFleetAutoscaleRole": { + "Arn": "arn:aws:iam::aws:policy/service-role/AmazonEC2SpotFleetAutoscaleRole", + "AttachmentCount": 0, + "CreateDate": "2016-08-19T18:27:22+00:00", + "DefaultVersionId": "v1", + "Document": { + "Statement": [ + { + "Action": [ + "ec2:DescribeSpotFleetRequests", + "ec2:ModifySpotFleetRequest" + ], + "Effect": "Allow", + "Resource": [ + "*" + ] + }, + { + "Action": [ + "cloudwatch:DescribeAlarms" + ], + "Effect": "Allow", + "Resource": [ + "*" + ] + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/service-role/", + "PolicyId": "ANPAIMFFRMIOBGDP2TAVE", + "PolicyName": "AmazonEC2SpotFleetAutoscaleRole", + "UpdateDate": "2016-08-19T18:27:22+00:00", + "VersionId": "v1" + }, + "AmazonEC2SpotFleetRole": { + "Arn": "arn:aws:iam::aws:policy/service-role/AmazonEC2SpotFleetRole", + "AttachmentCount": 0, + "CreateDate": "2016-11-10T21:19:35+00:00", + "DefaultVersionId": "v3", + "Document": { + "Statement": [ + { + "Action": [ + "ec2:DescribeImages", + "ec2:DescribeSubnets", + "ec2:RequestSpotInstances", + "ec2:TerminateInstances", + "ec2:DescribeInstanceStatus", + "iam:PassRole" + ], + "Effect": "Allow", + "Resource": [ + "*" + ] + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/service-role/", + "PolicyId": "ANPAIMRTKHWK7ESSNETSW", + "PolicyName": "AmazonEC2SpotFleetRole", + "UpdateDate": "2016-11-10T21:19:35+00:00", + "VersionId": "v3" + }, + "AmazonEC2SpotFleetTaggingRole": { + "Arn": "arn:aws:iam::aws:policy/service-role/AmazonEC2SpotFleetTaggingRole", + "AttachmentCount": 0, + "CreateDate": "2017-07-26T19:10:35+00:00", + "DefaultVersionId": "v2", + "Document": { + "Statement": [ + { + "Action": [ + "ec2:DescribeImages", + "ec2:DescribeSubnets", + "ec2:RequestSpotInstances", + "ec2:TerminateInstances", + "ec2:DescribeInstanceStatus", + "ec2:CreateTags" + ], + "Effect": "Allow", + "Resource": [ + "*" + ] + }, + { + "Action": "iam:PassRole", + "Condition": { + "StringEquals": { + "iam:PassedToService": "ec2.amazonaws.com" + } + }, + "Effect": "Allow", + "Resource": [ + "*" + ] + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/service-role/", + "PolicyId": "ANPAJ5U6UMLCEYLX5OLC4", + "PolicyName": "AmazonEC2SpotFleetTaggingRole", + "UpdateDate": "2017-07-26T19:10:35+00:00", + "VersionId": "v2" + }, + "AmazonESFullAccess": { + "Arn": "arn:aws:iam::aws:policy/AmazonESFullAccess", + "AttachmentCount": 0, + "CreateDate": "2015-10-01T19:14:00+00:00", + "DefaultVersionId": "v1", + "Document": { + "Statement": [ + { + "Action": [ + "es:*" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAJM6ZTCU24QL5PZCGC", + "PolicyName": "AmazonESFullAccess", + "UpdateDate": "2015-10-01T19:14:00+00:00", + "VersionId": "v1" + }, + "AmazonESReadOnlyAccess": { + "Arn": "arn:aws:iam::aws:policy/AmazonESReadOnlyAccess", + "AttachmentCount": 0, + "CreateDate": "2015-10-01T19:18:24+00:00", + "DefaultVersionId": "v1", + "Document": { + "Statement": [ + { + "Action": [ + "es:Describe*", + "es:List*" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAJUDMRLOQ7FPAR46FQ", + "PolicyName": "AmazonESReadOnlyAccess", + "UpdateDate": "2015-10-01T19:18:24+00:00", + "VersionId": "v1" + }, + "AmazonElastiCacheFullAccess": { + "Arn": "arn:aws:iam::aws:policy/AmazonElastiCacheFullAccess", + "AttachmentCount": 0, + "CreateDate": "2015-02-06T18:40:20+00:00", + "DefaultVersionId": "v1", + "Document": { + "Statement": [ + { + "Action": "elasticache:*", + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAIA2V44CPHAUAAECKG", + "PolicyName": "AmazonElastiCacheFullAccess", + "UpdateDate": "2015-02-06T18:40:20+00:00", + "VersionId": "v1" + }, + "AmazonElastiCacheReadOnlyAccess": { + "Arn": "arn:aws:iam::aws:policy/AmazonElastiCacheReadOnlyAccess", + "AttachmentCount": 0, + "CreateDate": "2015-02-06T18:40:21+00:00", + "DefaultVersionId": "v1", + "Document": { + "Statement": [ + { + "Action": [ + "elasticache:Describe*" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAIPDACSNQHSENWAKM2", + "PolicyName": "AmazonElastiCacheReadOnlyAccess", + "UpdateDate": "2015-02-06T18:40:21+00:00", + "VersionId": "v1" + }, + "AmazonElasticFileSystemFullAccess": { + "Arn": "arn:aws:iam::aws:policy/AmazonElasticFileSystemFullAccess", + "AttachmentCount": 0, + "CreateDate": "2017-08-14T10:18:34+00:00", + "DefaultVersionId": "v3", + "Document": { + "Statement": [ + { + "Action": [ + "ec2:CreateNetworkInterface", + "ec2:DeleteNetworkInterface", + "ec2:DescribeAvailabilityZones", + "ec2:DescribeNetworkInterfaceAttribute", + "ec2:DescribeNetworkInterfaces", + "ec2:DescribeSecurityGroups", + "ec2:DescribeSubnets", + "ec2:DescribeVpcAttribute", + "ec2:DescribeVpcs", + "ec2:ModifyNetworkInterfaceAttribute", + "elasticfilesystem:*", + "kms:DescribeKey", + "kms:ListAliases" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAJKXTMNVQGIDNCKPBC", + "PolicyName": "AmazonElasticFileSystemFullAccess", + "UpdateDate": "2017-08-14T10:18:34+00:00", + "VersionId": "v3" + }, + "AmazonElasticFileSystemReadOnlyAccess": { + "Arn": "arn:aws:iam::aws:policy/AmazonElasticFileSystemReadOnlyAccess", + "AttachmentCount": 0, + "CreateDate": "2017-08-14T10:09:49+00:00", + "DefaultVersionId": "v3", + "Document": { + "Statement": [ + { + "Action": [ + "ec2:DescribeAvailabilityZones", + "ec2:DescribeNetworkInterfaceAttribute", + "ec2:DescribeNetworkInterfaces", + "ec2:DescribeSecurityGroups", + "ec2:DescribeSubnets", + "ec2:DescribeVpcAttribute", + "ec2:DescribeVpcs", + "elasticfilesystem:Describe*", + "kms:ListAliases" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAIPN5S4NE5JJOKVC4Y", + "PolicyName": "AmazonElasticFileSystemReadOnlyAccess", + "UpdateDate": "2017-08-14T10:09:49+00:00", + "VersionId": "v3" + }, + "AmazonElasticMapReduceFullAccess": { + "Arn": "arn:aws:iam::aws:policy/AmazonElasticMapReduceFullAccess", + "AttachmentCount": 0, + "CreateDate": "2017-09-20T19:27:37+00:00", + "DefaultVersionId": "v5", + "Document": { + "Statement": [ + { + "Action": [ + "cloudwatch:*", + "cloudformation:CreateStack", + "cloudformation:DescribeStackEvents", + "ec2:AuthorizeSecurityGroupIngress", + "ec2:AuthorizeSecurityGroupEgress", + "ec2:CancelSpotInstanceRequests", + "ec2:CreateRoute", + "ec2:CreateSecurityGroup", + "ec2:CreateTags", + "ec2:DeleteRoute", + "ec2:DeleteTags", + "ec2:DeleteSecurityGroup", + "ec2:DescribeAvailabilityZones", + "ec2:DescribeAccountAttributes", + "ec2:DescribeInstances", + "ec2:DescribeKeyPairs", + "ec2:DescribeRouteTables", + "ec2:DescribeSecurityGroups", + "ec2:DescribeSpotInstanceRequests", + "ec2:DescribeSpotPriceHistory", + "ec2:DescribeSubnets", + "ec2:DescribeVpcAttribute", + "ec2:DescribeVpcs", + "ec2:DescribeRouteTables", + "ec2:DescribeNetworkAcls", + "ec2:CreateVpcEndpoint", + "ec2:ModifyImageAttribute", + "ec2:ModifyInstanceAttribute", + "ec2:RequestSpotInstances", + "ec2:RevokeSecurityGroupEgress", + "ec2:RunInstances", + "ec2:TerminateInstances", + "elasticmapreduce:*", + "iam:GetPolicy", + "iam:GetPolicyVersion", + "iam:ListRoles", + "iam:PassRole", + "kms:List*", + "s3:*", + "sdb:*", + "support:CreateCase", + "support:DescribeServices", + "support:DescribeSeverityLevels" + ], + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": "iam:CreateServiceLinkedRole", + "Condition": { + "StringLike": { + "iam:AWSServiceName": "elasticmapreduce.amazonaws.com" + } + }, + "Effect": "Allow", + "Resource": "arn:aws:iam::*:role/aws-service-role/elasticmapreduce.amazonaws.com/AWSServiceRoleForEMRCleanup" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAIZP5JFP3AMSGINBB2", + "PolicyName": "AmazonElasticMapReduceFullAccess", + "UpdateDate": "2017-09-20T19:27:37+00:00", + "VersionId": "v5" + }, + "AmazonElasticMapReduceReadOnlyAccess": { + "Arn": "arn:aws:iam::aws:policy/AmazonElasticMapReduceReadOnlyAccess", + "AttachmentCount": 0, + "CreateDate": "2017-05-22T23:00:19+00:00", + "DefaultVersionId": "v2", + "Document": { + "Statement": [ + { + "Action": [ + "elasticmapreduce:Describe*", + "elasticmapreduce:List*", + "elasticmapreduce:ViewEventsFromAllClustersInConsole", + "s3:GetObject", + "s3:ListAllMyBuckets", + "s3:ListBucket", + "sdb:Select", + "cloudwatch:GetMetricStatistics" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAIHP6NH2S6GYFCOINC", + "PolicyName": "AmazonElasticMapReduceReadOnlyAccess", + "UpdateDate": "2017-05-22T23:00:19+00:00", + "VersionId": "v2" + }, + "AmazonElasticMapReduceRole": { + "Arn": "arn:aws:iam::aws:policy/service-role/AmazonElasticMapReduceRole", + "AttachmentCount": 0, + "CreateDate": "2017-07-17T21:29:50+00:00", + "DefaultVersionId": "v8", + "Document": { + "Statement": [ + { + "Action": [ + "ec2:AuthorizeSecurityGroupEgress", + "ec2:AuthorizeSecurityGroupIngress", + "ec2:CancelSpotInstanceRequests", + "ec2:CreateNetworkInterface", + "ec2:CreateSecurityGroup", + "ec2:CreateTags", + "ec2:DeleteNetworkInterface", + "ec2:DeleteSecurityGroup", + "ec2:DeleteTags", + "ec2:DescribeAvailabilityZones", + "ec2:DescribeAccountAttributes", + "ec2:DescribeDhcpOptions", + "ec2:DescribeImages", + "ec2:DescribeInstanceStatus", + "ec2:DescribeInstances", + "ec2:DescribeKeyPairs", + "ec2:DescribeNetworkAcls", + "ec2:DescribeNetworkInterfaces", + "ec2:DescribePrefixLists", + "ec2:DescribeRouteTables", + "ec2:DescribeSecurityGroups", + "ec2:DescribeSpotInstanceRequests", + "ec2:DescribeSpotPriceHistory", + "ec2:DescribeSubnets", + "ec2:DescribeTags", + "ec2:DescribeVpcAttribute", + "ec2:DescribeVpcEndpoints", + "ec2:DescribeVpcEndpointServices", + "ec2:DescribeVpcs", + "ec2:DetachNetworkInterface", + "ec2:ModifyImageAttribute", + "ec2:ModifyInstanceAttribute", + "ec2:RequestSpotInstances", + "ec2:RevokeSecurityGroupEgress", + "ec2:RunInstances", + "ec2:TerminateInstances", + "ec2:DeleteVolume", + "ec2:DescribeVolumeStatus", + "ec2:DescribeVolumes", + "ec2:DetachVolume", + "iam:GetRole", + "iam:GetRolePolicy", + "iam:ListInstanceProfiles", + "iam:ListRolePolicies", + "iam:PassRole", + "s3:CreateBucket", + "s3:Get*", + "s3:List*", + "sdb:BatchPutAttributes", + "sdb:Select", + "sqs:CreateQueue", + "sqs:Delete*", + "sqs:GetQueue*", + "sqs:PurgeQueue", + "sqs:ReceiveMessage", + "cloudwatch:PutMetricAlarm", + "cloudwatch:DescribeAlarms", + "cloudwatch:DeleteAlarms", + "application-autoscaling:RegisterScalableTarget", + "application-autoscaling:DeregisterScalableTarget", + "application-autoscaling:PutScalingPolicy", + "application-autoscaling:DeleteScalingPolicy", + "application-autoscaling:Describe*" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/service-role/", + "PolicyId": "ANPAIDI2BQT2LKXZG36TW", + "PolicyName": "AmazonElasticMapReduceRole", + "UpdateDate": "2017-07-17T21:29:50+00:00", + "VersionId": "v8" + }, + "AmazonElasticMapReduceforAutoScalingRole": { + "Arn": "arn:aws:iam::aws:policy/service-role/AmazonElasticMapReduceforAutoScalingRole", + "AttachmentCount": 0, + "CreateDate": "2016-11-18T01:09:10+00:00", + "DefaultVersionId": "v1", + "Document": { + "Statement": [ + { + "Action": [ + "cloudwatch:DescribeAlarms", + "elasticmapreduce:ListInstanceGroups", + "elasticmapreduce:ModifyInstanceGroups" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/service-role/", + "PolicyId": "ANPAJSVXG6QHPE6VHDZ4Q", + "PolicyName": "AmazonElasticMapReduceforAutoScalingRole", + "UpdateDate": "2016-11-18T01:09:10+00:00", + "VersionId": "v1" + }, + "AmazonElasticMapReduceforEC2Role": { + "Arn": "arn:aws:iam::aws:policy/service-role/AmazonElasticMapReduceforEC2Role", + "AttachmentCount": 0, + "CreateDate": "2017-08-11T23:57:30+00:00", + "DefaultVersionId": "v3", + "Document": { + "Statement": [ + { + "Action": [ + "cloudwatch:*", + "dynamodb:*", + "ec2:Describe*", + "elasticmapreduce:Describe*", + "elasticmapreduce:ListBootstrapActions", + "elasticmapreduce:ListClusters", + "elasticmapreduce:ListInstanceGroups", + "elasticmapreduce:ListInstances", + "elasticmapreduce:ListSteps", + "kinesis:CreateStream", + "kinesis:DeleteStream", + "kinesis:DescribeStream", + "kinesis:GetRecords", + "kinesis:GetShardIterator", + "kinesis:MergeShards", + "kinesis:PutRecord", + "kinesis:SplitShard", + "rds:Describe*", + "s3:*", + "sdb:*", + "sns:*", + "sqs:*", + "glue:CreateDatabase", + "glue:UpdateDatabase", + "glue:DeleteDatabase", + "glue:GetDatabase", + "glue:GetDatabases", + "glue:CreateTable", + "glue:UpdateTable", + "glue:DeleteTable", + "glue:GetTable", + "glue:GetTables", + "glue:GetTableVersions", + "glue:CreatePartition", + "glue:BatchCreatePartition", + "glue:UpdatePartition", + "glue:DeletePartition", + "glue:BatchDeletePartition", + "glue:GetPartition", + "glue:GetPartitions", + "glue:BatchGetPartition", + "glue:CreateUserDefinedFunction", + "glue:UpdateUserDefinedFunction", + "glue:DeleteUserDefinedFunction", + "glue:GetUserDefinedFunction", + "glue:GetUserDefinedFunctions" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/service-role/", + "PolicyId": "ANPAIGALS5RCDLZLB3PGS", + "PolicyName": "AmazonElasticMapReduceforEC2Role", + "UpdateDate": "2017-08-11T23:57:30+00:00", + "VersionId": "v3" + }, + "AmazonElasticTranscoderFullAccess": { + "Arn": "arn:aws:iam::aws:policy/AmazonElasticTranscoderFullAccess", + "AttachmentCount": 0, + "CreateDate": "2015-02-06T18:40:24+00:00", + "DefaultVersionId": "v1", + "Document": { + "Statement": [ + { + "Action": [ + "elastictranscoder:*", + "cloudfront:*", + "s3:List*", + "s3:Put*", + "s3:Get*", + "s3:*MultipartUpload*", + "iam:CreateRole", + "iam:GetRolePolicy", + "iam:PassRole", + "iam:PutRolePolicy", + "iam:List*", + "sns:CreateTopic", + "sns:List*" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAJ4D5OJU75P5ZJZVNY", + "PolicyName": "AmazonElasticTranscoderFullAccess", + "UpdateDate": "2015-02-06T18:40:24+00:00", + "VersionId": "v1" + }, + "AmazonElasticTranscoderJobsSubmitter": { + "Arn": "arn:aws:iam::aws:policy/AmazonElasticTranscoderJobsSubmitter", + "AttachmentCount": 0, + "CreateDate": "2015-02-06T18:40:25+00:00", + "DefaultVersionId": "v1", + "Document": { + "Statement": [ + { + "Action": [ + "elastictranscoder:Read*", + "elastictranscoder:List*", + "elastictranscoder:*Job", + "elastictranscoder:*Preset", + "s3:List*", + "iam:List*", + "sns:List*" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAIN5WGARIKZ3E2UQOU", + "PolicyName": "AmazonElasticTranscoderJobsSubmitter", + "UpdateDate": "2015-02-06T18:40:25+00:00", + "VersionId": "v1" + }, + "AmazonElasticTranscoderReadOnlyAccess": { + "Arn": "arn:aws:iam::aws:policy/AmazonElasticTranscoderReadOnlyAccess", + "AttachmentCount": 0, + "CreateDate": "2015-02-06T18:40:26+00:00", + "DefaultVersionId": "v1", + "Document": { + "Statement": [ + { + "Action": [ + "elastictranscoder:Read*", + "elastictranscoder:List*", + "s3:List*", + "iam:List*", + "sns:List*" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAJGPP7GPMJRRJMEP3Q", + "PolicyName": "AmazonElasticTranscoderReadOnlyAccess", + "UpdateDate": "2015-02-06T18:40:26+00:00", + "VersionId": "v1" + }, + "AmazonElasticTranscoderRole": { + "Arn": "arn:aws:iam::aws:policy/service-role/AmazonElasticTranscoderRole", + "AttachmentCount": 0, + "CreateDate": "2015-02-06T18:41:26+00:00", + "DefaultVersionId": "v1", + "Document": { + "Statement": [ + { + "Action": [ + "s3:ListBucket", + "s3:Put*", + "s3:Get*", + "s3:*MultipartUpload*" + ], + "Effect": "Allow", + "Resource": [ + "*" + ], + "Sid": "1" + }, + { + "Action": [ + "sns:Publish" + ], + "Effect": "Allow", + "Resource": [ + "*" + ], + "Sid": "2" + }, + { + "Action": [ + "s3:*Policy*", + "sns:*Permission*", + "sns:*Delete*", + "s3:*Delete*", + "sns:*Remove*" + ], + "Effect": "Deny", + "Resource": [ + "*" + ], + "Sid": "3" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/service-role/", + "PolicyId": "ANPAJNW3WMKVXFJ2KPIQ2", + "PolicyName": "AmazonElasticTranscoderRole", + "UpdateDate": "2015-02-06T18:41:26+00:00", + "VersionId": "v1" + }, + "AmazonElasticsearchServiceRolePolicy": { + "Arn": "arn:aws:iam::aws:policy/aws-service-role/AmazonElasticsearchServiceRolePolicy", + "AttachmentCount": 0, + "CreateDate": "2017-07-07T00:15:31+00:00", + "DefaultVersionId": "v1", + "Document": { + "Statement": [ + { + "Action": [ + "ec2:CreateNetworkInterface", + "ec2:DeleteNetworkInterface", + "ec2:DescribeNetworkInterfaces", + "ec2:ModifyNetworkInterfaceAttribute", + "ec2:DescribeSecurityGroups", + "ec2:DescribeSubnets" + ], + "Effect": "Allow", + "Resource": "*", + "Sid": "Stmt1480452973134" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/aws-service-role/", + "PolicyId": "ANPAJFEWZPHXKLCVHEUIC", + "PolicyName": "AmazonElasticsearchServiceRolePolicy", + "UpdateDate": "2017-07-07T00:15:31+00:00", + "VersionId": "v1" + }, + "AmazonGlacierFullAccess": { + "Arn": "arn:aws:iam::aws:policy/AmazonGlacierFullAccess", + "AttachmentCount": 0, + "CreateDate": "2015-02-06T18:40:28+00:00", + "DefaultVersionId": "v1", + "Document": { + "Statement": [ + { + "Action": "glacier:*", + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAJQSTZJWB2AXXAKHVQ", + "PolicyName": "AmazonGlacierFullAccess", + "UpdateDate": "2015-02-06T18:40:28+00:00", + "VersionId": "v1" + }, + "AmazonGlacierReadOnlyAccess": { + "Arn": "arn:aws:iam::aws:policy/AmazonGlacierReadOnlyAccess", + "AttachmentCount": 0, + "CreateDate": "2016-05-05T18:46:10+00:00", + "DefaultVersionId": "v2", + "Document": { + "Statement": [ + { + "Action": [ + "glacier:DescribeJob", + "glacier:DescribeVault", + "glacier:GetDataRetrievalPolicy", + "glacier:GetJobOutput", + "glacier:GetVaultAccessPolicy", + "glacier:GetVaultLock", + "glacier:GetVaultNotifications", + "glacier:ListJobs", + "glacier:ListMultipartUploads", + "glacier:ListParts", + "glacier:ListTagsForVault", + "glacier:ListVaults" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAI2D5NJKMU274MET4E", + "PolicyName": "AmazonGlacierReadOnlyAccess", + "UpdateDate": "2016-05-05T18:46:10+00:00", + "VersionId": "v2" + }, + "AmazonInspectorFullAccess": { + "Arn": "arn:aws:iam::aws:policy/AmazonInspectorFullAccess", + "AttachmentCount": 0, + "CreateDate": "2017-09-12T17:42:57+00:00", + "DefaultVersionId": "v3", + "Document": { + "Statement": [ + { + "Action": [ + "inspector:*", + "ec2:DescribeInstances", + "ec2:DescribeTags", + "sns:ListTopics", + "events:DescribeRule", + "events:ListRuleNamesByTarget" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAI7Y6NTA27NWNA5U5E", + "PolicyName": "AmazonInspectorFullAccess", + "UpdateDate": "2017-09-12T17:42:57+00:00", + "VersionId": "v3" + }, + "AmazonInspectorReadOnlyAccess": { + "Arn": "arn:aws:iam::aws:policy/AmazonInspectorReadOnlyAccess", + "AttachmentCount": 0, + "CreateDate": "2017-09-12T16:53:06+00:00", + "DefaultVersionId": "v3", + "Document": { + "Statement": [ + { + "Action": [ + "inspector:Describe*", + "inspector:Get*", + "inspector:List*", + "inspector:LocalizeText", + "inspector:Preview*", + "ec2:DescribeInstances", + "ec2:DescribeTags", + "sns:ListTopics", + "events:DescribeRule", + "events:ListRuleNamesByTarget" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAJXQNTHTEJ2JFRN2SE", + "PolicyName": "AmazonInspectorReadOnlyAccess", + "UpdateDate": "2017-09-12T16:53:06+00:00", + "VersionId": "v3" + }, + "AmazonKinesisAnalyticsFullAccess": { + "Arn": "arn:aws:iam::aws:policy/AmazonKinesisAnalyticsFullAccess", + "AttachmentCount": 0, + "CreateDate": "2016-09-21T19:01:14+00:00", + "DefaultVersionId": "v1", + "Document": { + "Statement": [ + { + "Action": "kinesisanalytics:*", + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": [ + "kinesis:CreateStream", + "kinesis:DeleteStream", + "kinesis:DescribeStream", + "kinesis:ListStreams", + "kinesis:PutRecord", + "kinesis:PutRecords" + ], + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": [ + "firehose:DescribeDeliveryStream", + "firehose:ListDeliveryStreams" + ], + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": [ + "cloudwatch:GetMetricStatistics", + "cloudwatch:ListMetrics" + ], + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": "logs:GetLogEvents", + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": [ + "iam:ListPolicyVersions", + "iam:ListRoles" + ], + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": "iam:PassRole", + "Effect": "Allow", + "Resource": "arn:aws:iam::*:role/service-role/kinesis-analytics*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAJQOSKHTXP43R7P5AC", + "PolicyName": "AmazonKinesisAnalyticsFullAccess", + "UpdateDate": "2016-09-21T19:01:14+00:00", + "VersionId": "v1" + }, + "AmazonKinesisAnalyticsReadOnly": { + "Arn": "arn:aws:iam::aws:policy/AmazonKinesisAnalyticsReadOnly", + "AttachmentCount": 0, + "CreateDate": "2016-09-21T18:16:43+00:00", + "DefaultVersionId": "v1", + "Document": { + "Statement": [ + { + "Action": [ + "kinesisanalytics:Describe*", + "kinesisanalytics:Get*", + "kinesisanalytics:List*" + ], + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": [ + "kinesis:DescribeStream", + "kinesis:ListStreams" + ], + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": [ + "firehose:DescribeDeliveryStream", + "firehose:ListDeliveryStreams" + ], + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": [ + "cloudwatch:GetMetricStatistics", + "cloudwatch:ListMetrics" + ], + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": "logs:GetLogEvents", + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": [ + "iam:ListPolicyVersions", + "iam:ListRoles" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAIJIEXZAFUK43U7ARK", + "PolicyName": "AmazonKinesisAnalyticsReadOnly", + "UpdateDate": "2016-09-21T18:16:43+00:00", + "VersionId": "v1" + }, + "AmazonKinesisFirehoseFullAccess": { + "Arn": "arn:aws:iam::aws:policy/AmazonKinesisFirehoseFullAccess", + "AttachmentCount": 0, + "CreateDate": "2015-10-07T18:45:26+00:00", + "DefaultVersionId": "v1", + "Document": { + "Statement": [ + { + "Action": [ + "firehose:*" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAJMZQMTZ7FRBFHHAHI", + "PolicyName": "AmazonKinesisFirehoseFullAccess", + "UpdateDate": "2015-10-07T18:45:26+00:00", + "VersionId": "v1" + }, + "AmazonKinesisFirehoseReadOnlyAccess": { + "Arn": "arn:aws:iam::aws:policy/AmazonKinesisFirehoseReadOnlyAccess", + "AttachmentCount": 0, + "CreateDate": "2015-10-07T18:43:39+00:00", + "DefaultVersionId": "v1", + "Document": { + "Statement": [ + { + "Action": [ + "firehose:Describe*", + "firehose:List*" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAJ36NT645INW4K24W6", + "PolicyName": "AmazonKinesisFirehoseReadOnlyAccess", + "UpdateDate": "2015-10-07T18:43:39+00:00", + "VersionId": "v1" + }, + "AmazonKinesisFullAccess": { + "Arn": "arn:aws:iam::aws:policy/AmazonKinesisFullAccess", + "AttachmentCount": 0, + "CreateDate": "2015-02-06T18:40:29+00:00", + "DefaultVersionId": "v1", + "Document": { + "Statement": [ + { + "Action": "kinesis:*", + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAIVF32HAMOXCUYRAYE", + "PolicyName": "AmazonKinesisFullAccess", + "UpdateDate": "2015-02-06T18:40:29+00:00", + "VersionId": "v1" + }, + "AmazonKinesisReadOnlyAccess": { + "Arn": "arn:aws:iam::aws:policy/AmazonKinesisReadOnlyAccess", + "AttachmentCount": 0, + "CreateDate": "2015-02-06T18:40:30+00:00", + "DefaultVersionId": "v1", + "Document": { + "Statement": [ + { + "Action": [ + "kinesis:Get*", + "kinesis:List*", + "kinesis:Describe*" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAIOCMTDT5RLKZ2CAJO", + "PolicyName": "AmazonKinesisReadOnlyAccess", + "UpdateDate": "2015-02-06T18:40:30+00:00", + "VersionId": "v1" + }, + "AmazonLexFullAccess": { + "Arn": "arn:aws:iam::aws:policy/AmazonLexFullAccess", + "AttachmentCount": 0, + "CreateDate": "2017-04-14T19:45:37+00:00", + "DefaultVersionId": "v3", + "Document": { + "Statement": [ + { + "Action": [ + "cloudwatch:GetMetricStatistics", + "cloudwatch:DescribeAlarms", + "cloudwatch:DescribeAlarmsForMetric", + "kms:DescribeKey", + "kms:ListAliases", + "lambda:GetPolicy", + "lambda:ListFunctions", + "lex:*", + "polly:DescribeVoices", + "polly:SynthesizeSpeech" + ], + "Effect": "Allow", + "Resource": [ + "*" + ] + }, + { + "Action": [ + "lambda:AddPermission", + "lambda:RemovePermission" + ], + "Condition": { + "StringLike": { + "lambda:Principal": "lex.amazonaws.com" + } + }, + "Effect": "Allow", + "Resource": "arn:aws:lambda:*:*:function:AmazonLex*" + }, + { + "Action": [ + "iam:GetRole", + "iam:DeleteRole" + ], + "Effect": "Allow", + "Resource": [ + "arn:aws:iam::*:role/aws-service-role/lex.amazonaws.com/AWSServiceRoleForLexBots", + "arn:aws:iam::*:role/aws-service-role/channels.lex.amazonaws.com/AWSServiceRoleForLexChannels" + ] + }, + { + "Action": [ + "iam:CreateServiceLinkedRole" + ], + "Condition": { + "StringLike": { + "iam:AWSServiceName": "lex.amazonaws.com" + } + }, + "Effect": "Allow", + "Resource": [ + "arn:aws:iam::*:role/aws-service-role/lex.amazonaws.com/AWSServiceRoleForLexBots" + ] + }, + { + "Action": [ + "iam:DetachRolePolicy" + ], + "Condition": { + "StringLike": { + "iam:PolicyArn": "arn:aws:iam::aws:policy/aws-service-role/AmazonLexBotPolicy" + } + }, + "Effect": "Allow", + "Resource": [ + "arn:aws:iam::*:role/aws-service-role/lex.amazonaws.com/AWSServiceRoleForLexBots" + ] + }, + { + "Action": [ + "iam:CreateServiceLinkedRole" + ], + "Condition": { + "StringLike": { + "iam:AWSServiceName": "channels.lex.amazonaws.com" + } + }, + "Effect": "Allow", + "Resource": [ + "arn:aws:iam::*:role/aws-service-role/channels.lex.amazonaws.com/AWSServiceRoleForLexChannels" + ] + }, + { + "Action": [ + "iam:DetachRolePolicy" + ], + "Condition": { + "StringLike": { + "iam:PolicyArn": "arn:aws:iam::aws:policy/aws-service-role/LexChannelPolicy" + } + }, + "Effect": "Allow", + "Resource": [ + "arn:aws:iam::*:role/aws-service-role/channels.lex.amazonaws.com/AWSServiceRoleForLexChannels" + ] + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAJVLXDHKVC23HRTKSI", + "PolicyName": "AmazonLexFullAccess", + "UpdateDate": "2017-04-14T19:45:37+00:00", + "VersionId": "v3" + }, + "AmazonLexReadOnly": { + "Arn": "arn:aws:iam::aws:policy/AmazonLexReadOnly", + "AttachmentCount": 0, + "CreateDate": "2017-04-11T23:13:33+00:00", + "DefaultVersionId": "v1", + "Document": { + "Statement": [ + { + "Action": [ + "lex:GetBot", + "lex:GetBotAlias", + "lex:GetBotAliases", + "lex:GetBots", + "lex:GetBotChannelAssociation", + "lex:GetBotChannelAssociations", + "lex:GetBotVersions", + "lex:GetBuiltinIntent", + "lex:GetBuiltinIntents", + "lex:GetBuiltinSlotTypes", + "lex:GetIntent", + "lex:GetIntents", + "lex:GetIntentVersions", + "lex:GetSlotType", + "lex:GetSlotTypes", + "lex:GetSlotTypeVersions", + "lex:GetUtterancesView" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAJGBI5LSMAJNDGBNAM", + "PolicyName": "AmazonLexReadOnly", + "UpdateDate": "2017-04-11T23:13:33+00:00", + "VersionId": "v1" + }, + "AmazonLexRunBotsOnly": { + "Arn": "arn:aws:iam::aws:policy/AmazonLexRunBotsOnly", + "AttachmentCount": 0, + "CreateDate": "2017-04-11T23:06:24+00:00", + "DefaultVersionId": "v1", + "Document": { + "Statement": [ + { + "Action": [ + "lex:PostContent", + "lex:PostText" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAJVZGB5CM3N6YWJHBE", + "PolicyName": "AmazonLexRunBotsOnly", + "UpdateDate": "2017-04-11T23:06:24+00:00", + "VersionId": "v1" + }, + "AmazonMachineLearningBatchPredictionsAccess": { + "Arn": "arn:aws:iam::aws:policy/AmazonMachineLearningBatchPredictionsAccess", + "AttachmentCount": 0, + "CreateDate": "2015-04-09T17:12:19+00:00", + "DefaultVersionId": "v1", + "Document": { + "Statement": [ + { + "Action": [ + "machinelearning:CreateBatchPrediction", + "machinelearning:DeleteBatchPrediction", + "machinelearning:DescribeBatchPredictions", + "machinelearning:GetBatchPrediction", + "machinelearning:UpdateBatchPrediction" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAILOI4HTQSFTF3GQSC", + "PolicyName": "AmazonMachineLearningBatchPredictionsAccess", + "UpdateDate": "2015-04-09T17:12:19+00:00", + "VersionId": "v1" + }, + "AmazonMachineLearningCreateOnlyAccess": { + "Arn": "arn:aws:iam::aws:policy/AmazonMachineLearningCreateOnlyAccess", + "AttachmentCount": 0, + "CreateDate": "2016-06-29T20:55:03+00:00", + "DefaultVersionId": "v2", + "Document": { + "Statement": [ + { + "Action": [ + "machinelearning:Add*", + "machinelearning:Create*", + "machinelearning:Delete*", + "machinelearning:Describe*", + "machinelearning:Get*" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAJDRUNIC2RYAMAT3CK", + "PolicyName": "AmazonMachineLearningCreateOnlyAccess", + "UpdateDate": "2016-06-29T20:55:03+00:00", + "VersionId": "v2" + }, + "AmazonMachineLearningFullAccess": { + "Arn": "arn:aws:iam::aws:policy/AmazonMachineLearningFullAccess", + "AttachmentCount": 0, + "CreateDate": "2015-04-09T17:25:41+00:00", + "DefaultVersionId": "v1", + "Document": { + "Statement": [ + { + "Action": [ + "machinelearning:*" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAIWKW6AGSGYOQ5ERHC", + "PolicyName": "AmazonMachineLearningFullAccess", + "UpdateDate": "2015-04-09T17:25:41+00:00", + "VersionId": "v1" + }, + "AmazonMachineLearningManageRealTimeEndpointOnlyAccess": { + "Arn": "arn:aws:iam::aws:policy/AmazonMachineLearningManageRealTimeEndpointOnlyAccess", + "AttachmentCount": 0, + "CreateDate": "2015-04-09T17:32:41+00:00", + "DefaultVersionId": "v1", + "Document": { + "Statement": [ + { + "Action": [ + "machinelearning:CreateRealtimeEndpoint", + "machinelearning:DeleteRealtimeEndpoint" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAJJL3PC3VCSVZP6OCI", + "PolicyName": "AmazonMachineLearningManageRealTimeEndpointOnlyAccess", + "UpdateDate": "2015-04-09T17:32:41+00:00", + "VersionId": "v1" + }, + "AmazonMachineLearningReadOnlyAccess": { + "Arn": "arn:aws:iam::aws:policy/AmazonMachineLearningReadOnlyAccess", + "AttachmentCount": 0, + "CreateDate": "2015-04-09T17:40:02+00:00", + "DefaultVersionId": "v1", + "Document": { + "Statement": [ + { + "Action": [ + "machinelearning:Describe*", + "machinelearning:Get*" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAIW5VYBCGEX56JCINC", + "PolicyName": "AmazonMachineLearningReadOnlyAccess", + "UpdateDate": "2015-04-09T17:40:02+00:00", + "VersionId": "v1" + }, + "AmazonMachineLearningRealTimePredictionOnlyAccess": { + "Arn": "arn:aws:iam::aws:policy/AmazonMachineLearningRealTimePredictionOnlyAccess", + "AttachmentCount": 0, + "CreateDate": "2015-04-09T17:44:06+00:00", + "DefaultVersionId": "v1", + "Document": { + "Statement": [ + { + "Action": [ + "machinelearning:Predict" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAIWMCNQPRWMWT36GVQ", + "PolicyName": "AmazonMachineLearningRealTimePredictionOnlyAccess", + "UpdateDate": "2015-04-09T17:44:06+00:00", + "VersionId": "v1" + }, + "AmazonMachineLearningRoleforRedshiftDataSource": { + "Arn": "arn:aws:iam::aws:policy/service-role/AmazonMachineLearningRoleforRedshiftDataSource", + "AttachmentCount": 0, + "CreateDate": "2015-04-09T17:05:26+00:00", + "DefaultVersionId": "v1", + "Document": { + "Statement": [ + { + "Action": [ + "ec2:AuthorizeSecurityGroupIngress", + "ec2:CreateSecurityGroup", + "ec2:DescribeInternetGateways", + "ec2:DescribeSecurityGroups", + "ec2:RevokeSecurityGroupIngress", + "redshift:AuthorizeClusterSecurityGroupIngress", + "redshift:CreateClusterSecurityGroup", + "redshift:DescribeClusters", + "redshift:DescribeClusterSecurityGroups", + "redshift:ModifyCluster", + "redshift:RevokeClusterSecurityGroupIngress", + "s3:GetBucketLocation", + "s3:GetBucketPolicy", + "s3:GetObject", + "s3:PutBucketPolicy", + "s3:PutObject" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/service-role/", + "PolicyId": "ANPAIQ5UDYYMNN42BM4AK", + "PolicyName": "AmazonMachineLearningRoleforRedshiftDataSource", + "UpdateDate": "2015-04-09T17:05:26+00:00", + "VersionId": "v1" + }, + "AmazonMacieFullAccess": { + "Arn": "arn:aws:iam::aws:policy/AmazonMacieFullAccess", + "AttachmentCount": 0, + "CreateDate": "2017-08-14T14:54:30+00:00", + "DefaultVersionId": "v1", + "Document": { + "Statement": [ + { + "Action": [ + "macie:*" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAJJF2N5FR6S5TZN5OA", + "PolicyName": "AmazonMacieFullAccess", + "UpdateDate": "2017-08-14T14:54:30+00:00", + "VersionId": "v1" + }, + "AmazonMacieServiceRole": { + "Arn": "arn:aws:iam::aws:policy/service-role/AmazonMacieServiceRole", + "AttachmentCount": 0, + "CreateDate": "2017-08-14T14:53:26+00:00", + "DefaultVersionId": "v1", + "Document": { + "Statement": [ + { + "Action": [ + "s3:Get*", + "s3:List*" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/service-role/", + "PolicyId": "ANPAJVV7PON3FPBL2PSGC", + "PolicyName": "AmazonMacieServiceRole", + "UpdateDate": "2017-08-14T14:53:26+00:00", + "VersionId": "v1" + }, + "AmazonMacieSetupRole": { + "Arn": "arn:aws:iam::aws:policy/service-role/AmazonMacieSetupRole", + "AttachmentCount": 0, + "CreateDate": "2017-08-14T14:53:34+00:00", + "DefaultVersionId": "v1", + "Document": { + "Statement": [ + { + "Action": [ + "cloudtrail:DescribeTrails", + "cloudtrail:GetEventSelectors", + "cloudtrail:GetTrailStatus", + "cloudtrail:ListTags", + "cloudtrail:LookupEvents", + "iam:ListAccountAliases", + "s3:GetBucket*", + "s3:ListBucket", + "s3:ListAllMyBuckets" + ], + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": [ + "cloudtrail:CreateTrail", + "cloudtrail:StartLogging", + "cloudtrail:StopLogging", + "cloudtrail:UpdateTrail", + "cloudtrail:DeleteTrail", + "cloudtrail:PutEventSelectors" + ], + "Effect": "Allow", + "Resource": "arn:aws:cloudtrail:*:*:trail/AWSMacieTrail-DO-NOT-EDIT" + }, + { + "Action": [ + "s3:CreateBucket", + "s3:DeleteBucket", + "s3:DeleteBucketPolicy", + "s3:DeleteBucketWebsite", + "s3:DeleteObject", + "s3:DeleteObjectTagging", + "s3:DeleteObjectVersion", + "s3:DeleteObjectVersionTagging", + "s3:DeleteReplicationConfiguration", + "s3:PutBucketPolicy" + ], + "Effect": "Allow", + "Resource": [ + "arn:aws:s3:::awsmacie-*", + "arn:aws:s3:::awsmacietrail-*", + "arn:aws:s3:::*-awsmacietrail-*" + ] + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/service-role/", + "PolicyId": "ANPAJ5DC6UBVKND7ADSKA", + "PolicyName": "AmazonMacieSetupRole", + "UpdateDate": "2017-08-14T14:53:34+00:00", + "VersionId": "v1" + }, + "AmazonMechanicalTurkFullAccess": { + "Arn": "arn:aws:iam::aws:policy/AmazonMechanicalTurkFullAccess", + "AttachmentCount": 0, + "CreateDate": "2015-12-11T19:08:19+00:00", + "DefaultVersionId": "v1", + "Document": { + "Statement": [ + { + "Action": [ + "mechanicalturk:*" + ], + "Effect": "Allow", + "Resource": [ + "*" + ] + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAJDGCL5BET73H5QIQC", + "PolicyName": "AmazonMechanicalTurkFullAccess", + "UpdateDate": "2015-12-11T19:08:19+00:00", + "VersionId": "v1" + }, + "AmazonMechanicalTurkReadOnly": { + "Arn": "arn:aws:iam::aws:policy/AmazonMechanicalTurkReadOnly", + "AttachmentCount": 0, + "CreateDate": "2017-02-27T21:45:50+00:00", + "DefaultVersionId": "v2", + "Document": { + "Statement": [ + { + "Action": [ + "mechanicalturk:Get*", + "mechanicalturk:Search*", + "mechanicalturk:List*" + ], + "Effect": "Allow", + "Resource": [ + "*" + ] + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAIO5IY3G3WXSX5PPRM", + "PolicyName": "AmazonMechanicalTurkReadOnly", + "UpdateDate": "2017-02-27T21:45:50+00:00", + "VersionId": "v2" + }, + "AmazonMobileAnalyticsFinancialReportAccess": { + "Arn": "arn:aws:iam::aws:policy/AmazonMobileAnalyticsFinancialReportAccess", + "AttachmentCount": 0, + "CreateDate": "2015-02-06T18:40:35+00:00", + "DefaultVersionId": "v1", + "Document": { + "Statement": [ + { + "Action": [ + "mobileanalytics:GetReports", + "mobileanalytics:GetFinancialReports" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAJKJHO2R27TXKCWBU4", + "PolicyName": "AmazonMobileAnalyticsFinancialReportAccess", + "UpdateDate": "2015-02-06T18:40:35+00:00", + "VersionId": "v1" + }, + "AmazonMobileAnalyticsFullAccess": { + "Arn": "arn:aws:iam::aws:policy/AmazonMobileAnalyticsFullAccess", + "AttachmentCount": 0, + "CreateDate": "2015-02-06T18:40:34+00:00", + "DefaultVersionId": "v1", + "Document": { + "Statement": [ + { + "Action": "mobileanalytics:*", + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAIJIKLU2IJ7WJ6DZFG", + "PolicyName": "AmazonMobileAnalyticsFullAccess", + "UpdateDate": "2015-02-06T18:40:34+00:00", + "VersionId": "v1" + }, + "AmazonMobileAnalyticsNon-financialReportAccess": { + "Arn": "arn:aws:iam::aws:policy/AmazonMobileAnalyticsNon-financialReportAccess", + "AttachmentCount": 0, + "CreateDate": "2015-02-06T18:40:36+00:00", + "DefaultVersionId": "v1", + "Document": { + "Statement": [ + { + "Action": "mobileanalytics:GetReports", + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAIQLKQ4RXPUBBVVRDE", + "PolicyName": "AmazonMobileAnalyticsNon-financialReportAccess", + "UpdateDate": "2015-02-06T18:40:36+00:00", + "VersionId": "v1" + }, + "AmazonMobileAnalyticsWriteOnlyAccess": { + "Arn": "arn:aws:iam::aws:policy/AmazonMobileAnalyticsWriteOnlyAccess", + "AttachmentCount": 0, + "CreateDate": "2015-02-06T18:40:37+00:00", + "DefaultVersionId": "v1", + "Document": { + "Statement": [ + { + "Action": "mobileanalytics:PutEvents", + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAJ5TAWBBQC2FAL3G6G", + "PolicyName": "AmazonMobileAnalyticsWriteOnlyAccess", + "UpdateDate": "2015-02-06T18:40:37+00:00", + "VersionId": "v1" + }, + "AmazonPollyFullAccess": { + "Arn": "arn:aws:iam::aws:policy/AmazonPollyFullAccess", + "AttachmentCount": 0, + "CreateDate": "2016-11-30T18:59:06+00:00", + "DefaultVersionId": "v1", + "Document": { + "Statement": [ + { + "Action": [ + "polly:*" + ], + "Effect": "Allow", + "Resource": [ + "*" + ] + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAJUZOYQU6XQYPR7EWS", + "PolicyName": "AmazonPollyFullAccess", + "UpdateDate": "2016-11-30T18:59:06+00:00", + "VersionId": "v1" + }, + "AmazonPollyReadOnlyAccess": { + "Arn": "arn:aws:iam::aws:policy/AmazonPollyReadOnlyAccess", + "AttachmentCount": 0, + "CreateDate": "2016-11-30T18:59:24+00:00", + "DefaultVersionId": "v1", + "Document": { + "Statement": [ + { + "Action": [ + "polly:DescribeVoices", + "polly:GetLexicon", + "polly:ListLexicons", + "polly:SynthesizeSpeech" + ], + "Effect": "Allow", + "Resource": [ + "*" + ] + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAJ5FENL3CVPL2FPDLA", + "PolicyName": "AmazonPollyReadOnlyAccess", + "UpdateDate": "2016-11-30T18:59:24+00:00", + "VersionId": "v1" + }, + "AmazonRDSDirectoryServiceAccess": { + "Arn": "arn:aws:iam::aws:policy/service-role/AmazonRDSDirectoryServiceAccess", + "AttachmentCount": 0, + "CreateDate": "2016-02-26T02:02:05+00:00", + "DefaultVersionId": "v1", + "Document": { + "Statement": [ + { + "Action": [ + "ds:DescribeDirectories", + "ds:AuthorizeApplication", + "ds:UnauthorizeApplication" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/service-role/", + "PolicyId": "ANPAIL4KBY57XWMYUHKUU", + "PolicyName": "AmazonRDSDirectoryServiceAccess", + "UpdateDate": "2016-02-26T02:02:05+00:00", + "VersionId": "v1" + }, + "AmazonRDSEnhancedMonitoringRole": { + "Arn": "arn:aws:iam::aws:policy/service-role/AmazonRDSEnhancedMonitoringRole", + "AttachmentCount": 1, + "CreateDate": "2015-11-11T19:58:29+00:00", + "DefaultVersionId": "v1", + "Document": { + "Statement": [ + { + "Action": [ + "logs:CreateLogGroup", + "logs:PutRetentionPolicy" + ], + "Effect": "Allow", + "Resource": [ + "arn:aws:logs:*:*:log-group:RDS*" + ], + "Sid": "EnableCreationAndManagementOfRDSCloudwatchLogGroups" + }, + { + "Action": [ + "logs:CreateLogStream", + "logs:PutLogEvents", + "logs:DescribeLogStreams", + "logs:GetLogEvents" + ], + "Effect": "Allow", + "Resource": [ + "arn:aws:logs:*:*:log-group:RDS*:log-stream:*" + ], + "Sid": "EnableCreationAndManagementOfRDSCloudwatchLogStreams" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/service-role/", + "PolicyId": "ANPAJV7BS425S4PTSSVGK", + "PolicyName": "AmazonRDSEnhancedMonitoringRole", + "UpdateDate": "2015-11-11T19:58:29+00:00", + "VersionId": "v1" + }, + "AmazonRDSFullAccess": { + "Arn": "arn:aws:iam::aws:policy/AmazonRDSFullAccess", + "AttachmentCount": 0, + "CreateDate": "2017-09-14T23:40:45+00:00", + "DefaultVersionId": "v4", + "Document": { + "Statement": [ + { + "Action": [ + "rds:*", + "cloudwatch:DescribeAlarms", + "cloudwatch:GetMetricStatistics", + "ec2:DescribeAccountAttributes", + "ec2:DescribeAvailabilityZones", + "ec2:DescribeInternetGateways", + "ec2:DescribeSecurityGroups", + "ec2:DescribeSubnets", + "ec2:DescribeVpcAttribute", + "ec2:DescribeVpcs", + "sns:ListSubscriptions", + "sns:ListTopics", + "logs:DescribeLogStreams", + "logs:GetLogEvents" + ], + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": "pi:*", + "Effect": "Allow", + "Resource": "arn:aws:pi:*:*:metrics/rds/*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAI3R4QMOG6Q5A4VWVG", + "PolicyName": "AmazonRDSFullAccess", + "UpdateDate": "2017-09-14T23:40:45+00:00", + "VersionId": "v4" + }, + "AmazonRDSReadOnlyAccess": { + "Arn": "arn:aws:iam::aws:policy/AmazonRDSReadOnlyAccess", + "AttachmentCount": 0, + "CreateDate": "2017-08-28T21:36:32+00:00", + "DefaultVersionId": "v3", + "Document": { + "Statement": [ + { + "Action": [ + "rds:Describe*", + "rds:ListTagsForResource", + "ec2:DescribeAccountAttributes", + "ec2:DescribeAvailabilityZones", + "ec2:DescribeInternetGateways", + "ec2:DescribeSecurityGroups", + "ec2:DescribeSubnets", + "ec2:DescribeVpcAttribute", + "ec2:DescribeVpcs" + ], + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": [ + "cloudwatch:GetMetricStatistics", + "logs:DescribeLogStreams", + "logs:GetLogEvents" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAJKTTTYV2IIHKLZ346", + "PolicyName": "AmazonRDSReadOnlyAccess", + "UpdateDate": "2017-08-28T21:36:32+00:00", + "VersionId": "v3" + }, + "AmazonRedshiftFullAccess": { + "Arn": "arn:aws:iam::aws:policy/AmazonRedshiftFullAccess", + "AttachmentCount": 0, + "CreateDate": "2017-09-19T18:27:44+00:00", + "DefaultVersionId": "v2", + "Document": { + "Statement": [ + { + "Action": [ + "redshift:*", + "ec2:DescribeAccountAttributes", + "ec2:DescribeAddresses", + "ec2:DescribeAvailabilityZones", + "ec2:DescribeSecurityGroups", + "ec2:DescribeSubnets", + "ec2:DescribeVpcs", + "ec2:DescribeInternetGateways", + "sns:CreateTopic", + "sns:Get*", + "sns:List*", + "cloudwatch:Describe*", + "cloudwatch:Get*", + "cloudwatch:List*", + "cloudwatch:PutMetricAlarm", + "cloudwatch:EnableAlarmActions", + "cloudwatch:DisableAlarmActions" + ], + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": "iam:CreateServiceLinkedRole", + "Condition": { + "StringLike": { + "iam:AWSServiceName": "redshift.amazonaws.com" + } + }, + "Effect": "Allow", + "Resource": "arn:aws:iam::*:role/aws-service-role/redshift.amazonaws.com/AWSServiceRoleForRedshift" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAISEKCHH4YDB46B5ZO", + "PolicyName": "AmazonRedshiftFullAccess", + "UpdateDate": "2017-09-19T18:27:44+00:00", + "VersionId": "v2" + }, + "AmazonRedshiftReadOnlyAccess": { + "Arn": "arn:aws:iam::aws:policy/AmazonRedshiftReadOnlyAccess", + "AttachmentCount": 0, + "CreateDate": "2015-02-06T18:40:51+00:00", + "DefaultVersionId": "v1", + "Document": { + "Statement": [ + { + "Action": [ + "redshift:Describe*", + "redshift:ViewQueriesInConsole", + "ec2:DescribeAccountAttributes", + "ec2:DescribeAddresses", + "ec2:DescribeAvailabilityZones", + "ec2:DescribeSecurityGroups", + "ec2:DescribeSubnets", + "ec2:DescribeVpcs", + "ec2:DescribeInternetGateways", + "sns:Get*", + "sns:List*", + "cloudwatch:Describe*", + "cloudwatch:List*", + "cloudwatch:Get*" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAIGD46KSON64QBSEZM", + "PolicyName": "AmazonRedshiftReadOnlyAccess", + "UpdateDate": "2015-02-06T18:40:51+00:00", + "VersionId": "v1" + }, + "AmazonRedshiftServiceLinkedRolePolicy": { + "Arn": "arn:aws:iam::aws:policy/aws-service-role/AmazonRedshiftServiceLinkedRolePolicy", + "AttachmentCount": 0, + "CreateDate": "2017-09-18T19:19:45+00:00", + "DefaultVersionId": "v1", + "Document": { + "Statement": [ + { + "Action": [ + "ec2:DescribeVpcs", + "ec2:DescribeSubnets", + "ec2:DescribeNetworkInterfaces", + "ec2:DescribeAddress", + "ec2:AssociateAddress", + "ec2:DisassociateAddress", + "ec2:CreateNetworkInterface", + "ec2:DeleteNetworkInterface", + "ec2:ModifyNetworkInterfaceAttribute" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/aws-service-role/", + "PolicyId": "ANPAJPY2VXNRUYOY3SRZS", + "PolicyName": "AmazonRedshiftServiceLinkedRolePolicy", + "UpdateDate": "2017-09-18T19:19:45+00:00", + "VersionId": "v1" + }, + "AmazonRekognitionFullAccess": { + "Arn": "arn:aws:iam::aws:policy/AmazonRekognitionFullAccess", + "AttachmentCount": 0, + "CreateDate": "2016-11-30T14:40:44+00:00", + "DefaultVersionId": "v1", + "Document": { + "Statement": [ + { + "Action": [ + "rekognition:*" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAIWDAOK6AIFDVX6TT6", + "PolicyName": "AmazonRekognitionFullAccess", + "UpdateDate": "2016-11-30T14:40:44+00:00", + "VersionId": "v1" + }, + "AmazonRekognitionReadOnlyAccess": { + "Arn": "arn:aws:iam::aws:policy/AmazonRekognitionReadOnlyAccess", + "AttachmentCount": 0, + "CreateDate": "2016-11-30T14:58:06+00:00", + "DefaultVersionId": "v1", + "Document": { + "Statement": [ + { + "Action": [ + "rekognition:CompareFaces", + "rekognition:DetectFaces", + "rekognition:DetectLabels", + "rekognition:ListCollections", + "rekognition:ListFaces", + "rekognition:SearchFaces", + "rekognition:SearchFacesByImage" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAILWSUHXUY4ES43SA4", + "PolicyName": "AmazonRekognitionReadOnlyAccess", + "UpdateDate": "2016-11-30T14:58:06+00:00", + "VersionId": "v1" + }, + "AmazonRoute53DomainsFullAccess": { + "Arn": "arn:aws:iam::aws:policy/AmazonRoute53DomainsFullAccess", + "AttachmentCount": 0, + "CreateDate": "2015-02-06T18:40:56+00:00", + "DefaultVersionId": "v1", + "Document": { + "Statement": [ + { + "Action": [ + "route53:CreateHostedZone", + "route53domains:*" + ], + "Effect": "Allow", + "Resource": [ + "*" + ] + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAIPAFBMIYUILMOKL6G", + "PolicyName": "AmazonRoute53DomainsFullAccess", + "UpdateDate": "2015-02-06T18:40:56+00:00", + "VersionId": "v1" + }, + "AmazonRoute53DomainsReadOnlyAccess": { + "Arn": "arn:aws:iam::aws:policy/AmazonRoute53DomainsReadOnlyAccess", + "AttachmentCount": 0, + "CreateDate": "2015-02-06T18:40:57+00:00", + "DefaultVersionId": "v1", + "Document": { + "Statement": [ + { + "Action": [ + "route53domains:Get*", + "route53domains:List*" + ], + "Effect": "Allow", + "Resource": [ + "*" + ] + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAIDRINP6PPTRXYVQCI", + "PolicyName": "AmazonRoute53DomainsReadOnlyAccess", + "UpdateDate": "2015-02-06T18:40:57+00:00", + "VersionId": "v1" + }, + "AmazonRoute53FullAccess": { + "Arn": "arn:aws:iam::aws:policy/AmazonRoute53FullAccess", + "AttachmentCount": 0, + "CreateDate": "2017-02-14T21:25:53+00:00", + "DefaultVersionId": "v2", + "Document": { + "Statement": [ + { + "Action": [ + "route53:*", + "route53domains:*", + "cloudfront:ListDistributions", + "elasticloadbalancing:DescribeLoadBalancers", + "elasticbeanstalk:DescribeEnvironments", + "s3:ListBucket", + "s3:GetBucketLocation", + "s3:GetBucketWebsiteConfiguration", + "ec2:DescribeVpcs", + "ec2:DescribeRegions", + "sns:ListTopics", + "sns:ListSubscriptionsByTopic", + "cloudwatch:DescribeAlarms", + "cloudwatch:GetMetricStatistics" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAJWVDLG5RPST6PHQ3A", + "PolicyName": "AmazonRoute53FullAccess", + "UpdateDate": "2017-02-14T21:25:53+00:00", + "VersionId": "v2" + }, + "AmazonRoute53ReadOnlyAccess": { + "Arn": "arn:aws:iam::aws:policy/AmazonRoute53ReadOnlyAccess", + "AttachmentCount": 0, + "CreateDate": "2016-11-15T21:15:16+00:00", + "DefaultVersionId": "v2", + "Document": { + "Statement": [ + { + "Action": [ + "route53:Get*", + "route53:List*", + "route53:TestDNSAnswer" + ], + "Effect": "Allow", + "Resource": [ + "*" + ] + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAITOYK2ZAOQFXV2JNC", + "PolicyName": "AmazonRoute53ReadOnlyAccess", + "UpdateDate": "2016-11-15T21:15:16+00:00", + "VersionId": "v2" + }, + "AmazonS3FullAccess": { + "Arn": "arn:aws:iam::aws:policy/AmazonS3FullAccess", + "AttachmentCount": 1, + "CreateDate": "2015-02-06T18:40:58+00:00", + "DefaultVersionId": "v1", + "Document": { + "Statement": [ + { + "Action": "s3:*", + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAIFIR6V6BVTRAHWINE", + "PolicyName": "AmazonS3FullAccess", + "UpdateDate": "2015-02-06T18:40:58+00:00", + "VersionId": "v1" + }, + "AmazonS3ReadOnlyAccess": { + "Arn": "arn:aws:iam::aws:policy/AmazonS3ReadOnlyAccess", + "AttachmentCount": 0, + "CreateDate": "2015-02-06T18:40:59+00:00", + "DefaultVersionId": "v1", + "Document": { + "Statement": [ + { + "Action": [ + "s3:Get*", + "s3:List*" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAIZTJ4DXE7G6AGAE6M", + "PolicyName": "AmazonS3ReadOnlyAccess", + "UpdateDate": "2015-02-06T18:40:59+00:00", + "VersionId": "v1" + }, + "AmazonSESFullAccess": { + "Arn": "arn:aws:iam::aws:policy/AmazonSESFullAccess", + "AttachmentCount": 0, + "CreateDate": "2015-02-06T18:41:02+00:00", + "DefaultVersionId": "v1", + "Document": { + "Statement": [ + { + "Action": [ + "ses:*" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAJ2P4NXCHAT7NDPNR4", + "PolicyName": "AmazonSESFullAccess", + "UpdateDate": "2015-02-06T18:41:02+00:00", + "VersionId": "v1" + }, + "AmazonSESReadOnlyAccess": { + "Arn": "arn:aws:iam::aws:policy/AmazonSESReadOnlyAccess", + "AttachmentCount": 0, + "CreateDate": "2015-02-06T18:41:03+00:00", + "DefaultVersionId": "v1", + "Document": { + "Statement": [ + { + "Action": [ + "ses:Get*", + "ses:List*" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAINV2XPFRMWJJNSCGI", + "PolicyName": "AmazonSESReadOnlyAccess", + "UpdateDate": "2015-02-06T18:41:03+00:00", + "VersionId": "v1" + }, + "AmazonSNSFullAccess": { + "Arn": "arn:aws:iam::aws:policy/AmazonSNSFullAccess", + "AttachmentCount": 0, + "CreateDate": "2015-02-06T18:41:05+00:00", + "DefaultVersionId": "v1", + "Document": { + "Statement": [ + { + "Action": [ + "sns:*" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAJWEKLCXXUNT2SOLSG", + "PolicyName": "AmazonSNSFullAccess", + "UpdateDate": "2015-02-06T18:41:05+00:00", + "VersionId": "v1" + }, + "AmazonSNSReadOnlyAccess": { + "Arn": "arn:aws:iam::aws:policy/AmazonSNSReadOnlyAccess", + "AttachmentCount": 0, + "CreateDate": "2015-02-06T18:41:06+00:00", + "DefaultVersionId": "v1", + "Document": { + "Statement": [ + { + "Action": [ + "sns:GetTopicAttributes", + "sns:List*" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAIZGQCQTFOFPMHSB6W", + "PolicyName": "AmazonSNSReadOnlyAccess", + "UpdateDate": "2015-02-06T18:41:06+00:00", + "VersionId": "v1" + }, + "AmazonSNSRole": { + "Arn": "arn:aws:iam::aws:policy/service-role/AmazonSNSRole", + "AttachmentCount": 0, + "CreateDate": "2015-02-06T18:41:30+00:00", + "DefaultVersionId": "v1", + "Document": { + "Statement": [ + { + "Action": [ + "logs:CreateLogGroup", + "logs:CreateLogStream", + "logs:PutLogEvents", + "logs:PutMetricFilter", + "logs:PutRetentionPolicy" + ], + "Effect": "Allow", + "Resource": [ + "*" + ] + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/service-role/", + "PolicyId": "ANPAJK5GQB7CIK7KHY2GA", + "PolicyName": "AmazonSNSRole", + "UpdateDate": "2015-02-06T18:41:30+00:00", + "VersionId": "v1" + }, + "AmazonSQSFullAccess": { + "Arn": "arn:aws:iam::aws:policy/AmazonSQSFullAccess", + "AttachmentCount": 0, + "CreateDate": "2015-02-06T18:41:07+00:00", + "DefaultVersionId": "v1", + "Document": { + "Statement": [ + { + "Action": [ + "sqs:*" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAI65L554VRJ33ECQS6", + "PolicyName": "AmazonSQSFullAccess", + "UpdateDate": "2015-02-06T18:41:07+00:00", + "VersionId": "v1" + }, + "AmazonSQSReadOnlyAccess": { + "Arn": "arn:aws:iam::aws:policy/AmazonSQSReadOnlyAccess", + "AttachmentCount": 0, + "CreateDate": "2015-02-06T18:41:08+00:00", + "DefaultVersionId": "v1", + "Document": { + "Statement": [ + { + "Action": [ + "sqs:GetQueueAttributes", + "sqs:ListQueues" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAIUGSSQY362XGCM6KW", + "PolicyName": "AmazonSQSReadOnlyAccess", + "UpdateDate": "2015-02-06T18:41:08+00:00", + "VersionId": "v1" + }, + "AmazonSSMAutomationApproverAccess": { + "Arn": "arn:aws:iam::aws:policy/AmazonSSMAutomationApproverAccess", + "AttachmentCount": 0, + "CreateDate": "2017-08-07T23:07:28+00:00", + "DefaultVersionId": "v1", + "Document": { + "Statement": [ + { + "Action": [ + "ssm:DescribeAutomationExecutions", + "ssm:GetAutomationExecution", + "ssm:SendAutomationSignal" + ], + "Effect": "Allow", + "Resource": [ + "*" + ] + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAIDSSXIRWBSLWWIORC", + "PolicyName": "AmazonSSMAutomationApproverAccess", + "UpdateDate": "2017-08-07T23:07:28+00:00", + "VersionId": "v1" + }, + "AmazonSSMAutomationRole": { + "Arn": "arn:aws:iam::aws:policy/service-role/AmazonSSMAutomationRole", + "AttachmentCount": 0, + "CreateDate": "2017-07-24T23:29:12+00:00", + "DefaultVersionId": "v5", + "Document": { + "Statement": [ + { + "Action": [ + "lambda:InvokeFunction" + ], + "Effect": "Allow", + "Resource": [ + "arn:aws:lambda:*:*:function:Automation*" + ] + }, + { + "Action": [ + "ec2:CreateImage", + "ec2:CopyImage", + "ec2:DeregisterImage", + "ec2:DescribeImages", + "ec2:DeleteSnapshot", + "ec2:StartInstances", + "ec2:RunInstances", + "ec2:StopInstances", + "ec2:TerminateInstances", + "ec2:DescribeInstanceStatus", + "ec2:CreateTags", + "ec2:DeleteTags", + "ec2:DescribeTags", + "cloudformation:CreateStack", + "cloudformation:DescribeStackEvents", + "cloudformation:DescribeStacks", + "cloudformation:UpdateStack", + "cloudformation:DeleteStack" + ], + "Effect": "Allow", + "Resource": [ + "*" + ] + }, + { + "Action": [ + "ssm:*" + ], + "Effect": "Allow", + "Resource": [ + "*" + ] + }, + { + "Action": [ + "sns:Publish" + ], + "Effect": "Allow", + "Resource": [ + "arn:aws:sns:*:*:Automation*" + ] + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/service-role/", + "PolicyId": "ANPAJIBQCTBCXD2XRNB6W", + "PolicyName": "AmazonSSMAutomationRole", + "UpdateDate": "2017-07-24T23:29:12+00:00", + "VersionId": "v5" + }, + "AmazonSSMFullAccess": { + "Arn": "arn:aws:iam::aws:policy/AmazonSSMFullAccess", + "AttachmentCount": 0, + "CreateDate": "2016-03-07T21:09:12+00:00", + "DefaultVersionId": "v2", + "Document": { + "Statement": [ + { + "Action": [ + "cloudwatch:PutMetricData", + "ds:CreateComputer", + "ds:DescribeDirectories", + "ec2:DescribeInstanceStatus", + "logs:*", + "ssm:*", + "ec2messages:*" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAJA7V6HI4ISQFMDYAG", + "PolicyName": "AmazonSSMFullAccess", + "UpdateDate": "2016-03-07T21:09:12+00:00", + "VersionId": "v2" + }, + "AmazonSSMMaintenanceWindowRole": { + "Arn": "arn:aws:iam::aws:policy/service-role/AmazonSSMMaintenanceWindowRole", + "AttachmentCount": 0, + "CreateDate": "2017-08-09T20:49:14+00:00", + "DefaultVersionId": "v2", + "Document": { + "Statement": [ + { + "Action": [ + "ssm:GetAutomationExecution", + "ssm:GetParameters", + "ssm:ListCommands", + "ssm:SendCommand", + "ssm:StartAutomationExecution" + ], + "Effect": "Allow", + "Resource": [ + "*" + ], + "Sid": "Stmt1477803259000" + }, + { + "Action": [ + "lambda:InvokeFunction" + ], + "Effect": "Allow", + "Resource": [ + "arn:aws:lambda:*:*:function:SSM*", + "arn:aws:lambda:*:*:function:*:SSM*" + ], + "Sid": "Stmt1477803259001" + }, + { + "Action": [ + "states:DescribeExecution", + "states:StartExecution" + ], + "Effect": "Allow", + "Resource": [ + "arn:aws:states:*:*:stateMachine:SSM*", + "arn:aws:states:*:*:execution:SSM*" + ], + "Sid": "Stmt1477803259002" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/service-role/", + "PolicyId": "ANPAJV3JNYSTZ47VOXYME", + "PolicyName": "AmazonSSMMaintenanceWindowRole", + "UpdateDate": "2017-08-09T20:49:14+00:00", + "VersionId": "v2" + }, + "AmazonSSMReadOnlyAccess": { + "Arn": "arn:aws:iam::aws:policy/AmazonSSMReadOnlyAccess", + "AttachmentCount": 0, + "CreateDate": "2015-05-29T17:44:19+00:00", + "DefaultVersionId": "v1", + "Document": { + "Statement": [ + { + "Action": [ + "ssm:Describe*", + "ssm:Get*", + "ssm:List*" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAJODSKQGGJTHRYZ5FC", + "PolicyName": "AmazonSSMReadOnlyAccess", + "UpdateDate": "2015-05-29T17:44:19+00:00", + "VersionId": "v1" + }, + "AmazonVPCCrossAccountNetworkInterfaceOperations": { + "Arn": "arn:aws:iam::aws:policy/AmazonVPCCrossAccountNetworkInterfaceOperations", + "AttachmentCount": 0, + "CreateDate": "2017-07-18T20:47:16+00:00", + "DefaultVersionId": "v1", + "Document": { + "Statement": [ + { + "Action": [ + "ec2:DescribeRouteTables", + "ec2:CreateRoute", + "ec2:DeleteRoute", + "ec2:ReplaceRoute" + ], + "Effect": "Allow", + "Resource": [ + "*" + ] + }, + { + "Action": [ + "ec2:DescribeNetworkInterfaces", + "ec2:CreateNetworkInterface", + "ec2:DeleteNetworkInterface", + "ec2:CreateNetworkInterfacePermission", + "ec2:ModifyNetworkInterfaceAttribute", + "ec2:DescribeNetworkInterfaceAttribute", + "ec2:DescribeVpcs", + "ec2:DescribeSubnets" + ], + "Effect": "Allow", + "Resource": [ + "*" + ] + }, + { + "Action": [ + "ec2:AssignPrivateIpAddresses", + "ec2:UnassignPrivateIpAddresses" + ], + "Effect": "Allow", + "Resource": [ + "*" + ] + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAJ53Y4ZY5OHP4CNRJC", + "PolicyName": "AmazonVPCCrossAccountNetworkInterfaceOperations", + "UpdateDate": "2017-07-18T20:47:16+00:00", + "VersionId": "v1" + }, + "AmazonVPCFullAccess": { + "Arn": "arn:aws:iam::aws:policy/AmazonVPCFullAccess", + "AttachmentCount": 1, + "CreateDate": "2015-12-17T17:25:44+00:00", + "DefaultVersionId": "v5", + "Document": { + "Statement": [ + { + "Action": [ + "ec2:AcceptVpcPeeringConnection", + "ec2:AllocateAddress", + "ec2:AssignPrivateIpAddresses", + "ec2:AssociateAddress", + "ec2:AssociateDhcpOptions", + "ec2:AssociateRouteTable", + "ec2:AttachClassicLinkVpc", + "ec2:AttachInternetGateway", + "ec2:AttachNetworkInterface", + "ec2:AttachVpnGateway", + "ec2:AuthorizeSecurityGroupEgress", + "ec2:AuthorizeSecurityGroupIngress", + "ec2:CreateCustomerGateway", + "ec2:CreateDhcpOptions", + "ec2:CreateFlowLogs", + "ec2:CreateInternetGateway", + "ec2:CreateNatGateway", + "ec2:CreateNetworkAcl", + "ec2:CreateNetworkAcl", + "ec2:CreateNetworkAclEntry", + "ec2:CreateNetworkInterface", + "ec2:CreateRoute", + "ec2:CreateRouteTable", + "ec2:CreateSecurityGroup", + "ec2:CreateSubnet", + "ec2:CreateTags", + "ec2:CreateVpc", + "ec2:CreateVpcEndpoint", + "ec2:CreateVpcPeeringConnection", + "ec2:CreateVpnConnection", + "ec2:CreateVpnConnectionRoute", + "ec2:CreateVpnGateway", + "ec2:DeleteCustomerGateway", + "ec2:DeleteDhcpOptions", + "ec2:DeleteFlowLogs", + "ec2:DeleteInternetGateway", + "ec2:DeleteNatGateway", + "ec2:DeleteNetworkAcl", + "ec2:DeleteNetworkAclEntry", + "ec2:DeleteNetworkInterface", + "ec2:DeleteRoute", + "ec2:DeleteRouteTable", + "ec2:DeleteSecurityGroup", + "ec2:DeleteSubnet", + "ec2:DeleteTags", + "ec2:DeleteVpc", + "ec2:DeleteVpcEndpoints", + "ec2:DeleteVpcPeeringConnection", + "ec2:DeleteVpnConnection", + "ec2:DeleteVpnConnectionRoute", + "ec2:DeleteVpnGateway", + "ec2:DescribeAddresses", + "ec2:DescribeAvailabilityZones", + "ec2:DescribeClassicLinkInstances", + "ec2:DescribeCustomerGateways", + "ec2:DescribeDhcpOptions", + "ec2:DescribeFlowLogs", + "ec2:DescribeInstances", + "ec2:DescribeInternetGateways", + "ec2:DescribeKeyPairs", + "ec2:DescribeMovingAddresses", + "ec2:DescribeNatGateways", + "ec2:DescribeNetworkAcls", + "ec2:DescribeNetworkInterfaceAttribute", + "ec2:DescribeNetworkInterfaces", + "ec2:DescribePrefixLists", + "ec2:DescribeRouteTables", + "ec2:DescribeSecurityGroups", + "ec2:DescribeSubnets", + "ec2:DescribeTags", + "ec2:DescribeVpcAttribute", + "ec2:DescribeVpcClassicLink", + "ec2:DescribeVpcEndpoints", + "ec2:DescribeVpcEndpointServices", + "ec2:DescribeVpcPeeringConnections", + "ec2:DescribeVpcs", + "ec2:DescribeVpnConnections", + "ec2:DescribeVpnGateways", + "ec2:DetachClassicLinkVpc", + "ec2:DetachInternetGateway", + "ec2:DetachNetworkInterface", + "ec2:DetachVpnGateway", + "ec2:DisableVgwRoutePropagation", + "ec2:DisableVpcClassicLink", + "ec2:DisassociateAddress", + "ec2:DisassociateRouteTable", + "ec2:EnableVgwRoutePropagation", + "ec2:EnableVpcClassicLink", + "ec2:ModifyNetworkInterfaceAttribute", + "ec2:ModifySubnetAttribute", + "ec2:ModifyVpcAttribute", + "ec2:ModifyVpcEndpoint", + "ec2:MoveAddressToVpc", + "ec2:RejectVpcPeeringConnection", + "ec2:ReleaseAddress", + "ec2:ReplaceNetworkAclAssociation", + "ec2:ReplaceNetworkAclEntry", + "ec2:ReplaceRoute", + "ec2:ReplaceRouteTableAssociation", + "ec2:ResetNetworkInterfaceAttribute", + "ec2:RestoreAddressToClassic", + "ec2:RevokeSecurityGroupEgress", + "ec2:RevokeSecurityGroupIngress", + "ec2:UnassignPrivateIpAddresses" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAJBWPGNOVKZD3JI2P2", + "PolicyName": "AmazonVPCFullAccess", + "UpdateDate": "2015-12-17T17:25:44+00:00", + "VersionId": "v5" + }, + "AmazonVPCReadOnlyAccess": { + "Arn": "arn:aws:iam::aws:policy/AmazonVPCReadOnlyAccess", + "AttachmentCount": 0, + "CreateDate": "2015-12-17T17:25:56+00:00", + "DefaultVersionId": "v4", + "Document": { + "Statement": [ + { + "Action": [ + "ec2:DescribeAddresses", + "ec2:DescribeClassicLinkInstances", + "ec2:DescribeCustomerGateways", + "ec2:DescribeDhcpOptions", + "ec2:DescribeFlowLogs", + "ec2:DescribeInternetGateways", + "ec2:DescribeMovingAddresses", + "ec2:DescribeNatGateways", + "ec2:DescribeNetworkAcls", + "ec2:DescribeNetworkInterfaceAttribute", + "ec2:DescribeNetworkInterfaces", + "ec2:DescribePrefixLists", + "ec2:DescribeRouteTables", + "ec2:DescribeSecurityGroups", + "ec2:DescribeSubnets", + "ec2:DescribeTags", + "ec2:DescribeVpcAttribute", + "ec2:DescribeVpcClassicLink", + "ec2:DescribeVpcEndpoints", + "ec2:DescribeVpcEndpointServices", + "ec2:DescribeVpcPeeringConnections", + "ec2:DescribeVpcs", + "ec2:DescribeVpnConnections", + "ec2:DescribeVpnGateways" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAIICZJNOJN36GTG6CM", + "PolicyName": "AmazonVPCReadOnlyAccess", + "UpdateDate": "2015-12-17T17:25:56+00:00", + "VersionId": "v4" + }, + "AmazonWorkMailFullAccess": { + "Arn": "arn:aws:iam::aws:policy/AmazonWorkMailFullAccess", + "AttachmentCount": 0, + "CreateDate": "2017-04-20T08:35:49+00:00", + "DefaultVersionId": "v3", + "Document": { + "Statement": [ + { + "Action": [ + "ds:AuthorizeApplication", + "ds:CheckAlias", + "ds:CreateAlias", + "ds:CreateDirectory", + "ds:CreateIdentityPoolDirectory", + "ds:CreateDomain", + "ds:DeleteAlias", + "ds:DeleteDirectory", + "ds:DescribeDirectories", + "ds:ExtendDirectory", + "ds:GetDirectoryLimits", + "ds:ListAuthorizedApplications", + "ds:UnauthorizeApplication", + "ec2:AuthorizeSecurityGroupEgress", + "ec2:AuthorizeSecurityGroupIngress", + "ec2:CreateNetworkInterface", + "ec2:CreateSecurityGroup", + "ec2:CreateSubnet", + "ec2:CreateTags", + "ec2:CreateVpc", + "ec2:DeleteSecurityGroup", + "ec2:DeleteSubnet", + "ec2:DeleteVpc", + "ec2:DescribeAvailabilityZones", + "ec2:DescribeDomains", + "ec2:DescribeRouteTables", + "ec2:DescribeSubnets", + "ec2:DescribeVpcs", + "ec2:RevokeSecurityGroupEgress", + "ec2:RevokeSecurityGroupIngress", + "kms:DescribeKey", + "kms:ListAliases", + "ses:*", + "workmail:*" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAJQVKNMT7SVATQ4AUY", + "PolicyName": "AmazonWorkMailFullAccess", + "UpdateDate": "2017-04-20T08:35:49+00:00", + "VersionId": "v3" + }, + "AmazonWorkMailReadOnlyAccess": { + "Arn": "arn:aws:iam::aws:policy/AmazonWorkMailReadOnlyAccess", + "AttachmentCount": 0, + "CreateDate": "2015-02-06T18:40:42+00:00", + "DefaultVersionId": "v1", + "Document": { + "Statement": [ + { + "Action": [ + "ses:Describe*", + "ses:Get*", + "workmail:Describe*", + "workmail:Get*", + "workmail:List*", + "workmail:Search*" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAJHF7J65E2QFKCWAJM", + "PolicyName": "AmazonWorkMailReadOnlyAccess", + "UpdateDate": "2015-02-06T18:40:42+00:00", + "VersionId": "v1" + }, + "AmazonWorkSpacesAdmin": { + "Arn": "arn:aws:iam::aws:policy/AmazonWorkSpacesAdmin", + "AttachmentCount": 0, + "CreateDate": "2016-08-18T23:08:42+00:00", + "DefaultVersionId": "v2", + "Document": { + "Statement": [ + { + "Action": [ + "workspaces:CreateWorkspaces", + "workspaces:DescribeWorkspaces", + "workspaces:RebootWorkspaces", + "workspaces:RebuildWorkspaces", + "workspaces:TerminateWorkspaces", + "workspaces:DescribeWorkspaceDirectories", + "workspaces:DescribeWorkspaceBundles", + "workspaces:ModifyWorkspaceProperties", + "workspaces:StopWorkspaces", + "workspaces:StartWorkspaces", + "workspaces:DescribeWorkspacesConnectionStatus", + "workspaces:CreateTags", + "workspaces:DeleteTags", + "workspaces:DescribeTags", + "kms:ListKeys", + "kms:ListAliases", + "kms:DescribeKey" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAJ26AU6ATUQCT5KVJU", + "PolicyName": "AmazonWorkSpacesAdmin", + "UpdateDate": "2016-08-18T23:08:42+00:00", + "VersionId": "v2" + }, + "AmazonWorkSpacesApplicationManagerAdminAccess": { + "Arn": "arn:aws:iam::aws:policy/AmazonWorkSpacesApplicationManagerAdminAccess", + "AttachmentCount": 0, + "CreateDate": "2015-04-09T14:03:18+00:00", + "DefaultVersionId": "v1", + "Document": { + "Statement": [ + { + "Action": "wam:AuthenticatePackager", + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAJPRL4KYETIH7XGTSS", + "PolicyName": "AmazonWorkSpacesApplicationManagerAdminAccess", + "UpdateDate": "2015-04-09T14:03:18+00:00", + "VersionId": "v1" + }, + "AmazonZocaloFullAccess": { + "Arn": "arn:aws:iam::aws:policy/AmazonZocaloFullAccess", + "AttachmentCount": 0, + "CreateDate": "2015-02-06T18:41:13+00:00", + "DefaultVersionId": "v1", + "Document": { + "Statement": [ + { + "Action": [ + "zocalo:*", + "ds:*", + "ec2:AuthorizeSecurityGroupEgress", + "ec2:AuthorizeSecurityGroupIngress", + "ec2:CreateNetworkInterface", + "ec2:CreateSecurityGroup", + "ec2:CreateSubnet", + "ec2:CreateTags", + "ec2:CreateVpc", + "ec2:DescribeAvailabilityZones", + "ec2:DescribeNetworkInterfaces", + "ec2:DescribeSubnets", + "ec2:DescribeVpcs", + "ec2:DeleteNetworkInterface", + "ec2:DeleteSecurityGroup", + "ec2:RevokeSecurityGroupEgress", + "ec2:RevokeSecurityGroupIngress" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAJLCDXYRINDMUXEVL6", + "PolicyName": "AmazonZocaloFullAccess", + "UpdateDate": "2015-02-06T18:41:13+00:00", + "VersionId": "v1" + }, + "AmazonZocaloReadOnlyAccess": { + "Arn": "arn:aws:iam::aws:policy/AmazonZocaloReadOnlyAccess", + "AttachmentCount": 0, + "CreateDate": "2015-02-06T18:41:14+00:00", + "DefaultVersionId": "v1", + "Document": { + "Statement": [ + { + "Action": [ + "zocalo:Describe*", + "ds:DescribeDirectories", + "ec2:DescribeVpcs", + "ec2:DescribeSubnets" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAISRCSSJNS3QPKZJPM", + "PolicyName": "AmazonZocaloReadOnlyAccess", + "UpdateDate": "2015-02-06T18:41:14+00:00", + "VersionId": "v1" + }, + "ApplicationAutoScalingForAmazonAppStreamAccess": { + "Arn": "arn:aws:iam::aws:policy/service-role/ApplicationAutoScalingForAmazonAppStreamAccess", + "AttachmentCount": 0, + "CreateDate": "2017-02-06T21:39:56+00:00", + "DefaultVersionId": "v1", + "Document": { + "Statement": [ + { + "Action": [ + "appstream:UpdateFleet", + "appstream:DescribeFleets" + ], + "Effect": "Allow", + "Resource": [ + "*" + ] + }, + { + "Action": [ + "cloudwatch:DescribeAlarms" + ], + "Effect": "Allow", + "Resource": [ + "*" + ] + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/service-role/", + "PolicyId": "ANPAIEL3HJCCWFVHA6KPG", + "PolicyName": "ApplicationAutoScalingForAmazonAppStreamAccess", + "UpdateDate": "2017-02-06T21:39:56+00:00", + "VersionId": "v1" + }, + "AutoScalingConsoleFullAccess": { + "Arn": "arn:aws:iam::aws:policy/AutoScalingConsoleFullAccess", + "AttachmentCount": 0, + "CreateDate": "2017-01-12T19:43:16+00:00", + "DefaultVersionId": "v1", + "Document": { + "Statement": [ + { + "Action": [ + "ec2:AuthorizeSecurityGroupIngress", + "ec2:CreateKeyPair", + "ec2:CreateSecurityGroup", + "ec2:DescribeAvailabilityZones", + "ec2:DescribeImages", + "ec2:DescribeKeyPairs", + "ec2:DescribeSecurityGroups", + "ec2:DescribeSubnets", + "ec2:DescribeVpcs", + "ec2:DescribeVpcClassicLink", + "ec2:ImportKeyPair" + ], + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": "elasticloadbalancing:Describe*", + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": [ + "cloudwatch:ListMetrics", + "cloudwatch:GetMetricStatistics", + "cloudwatch:PutMetricAlarm", + "cloudwatch:Describe*" + ], + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": "autoscaling:*", + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": [ + "sns:ListSubscriptions", + "sns:ListTopics" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAIYEN6FJGYYWJFFCZW", + "PolicyName": "AutoScalingConsoleFullAccess", + "UpdateDate": "2017-01-12T19:43:16+00:00", + "VersionId": "v1" + }, + "AutoScalingConsoleReadOnlyAccess": { + "Arn": "arn:aws:iam::aws:policy/AutoScalingConsoleReadOnlyAccess", + "AttachmentCount": 0, + "CreateDate": "2017-01-12T19:48:53+00:00", + "DefaultVersionId": "v1", + "Document": { + "Statement": [ + { + "Action": [ + "ec2:DescribeVpcs", + "ec2:DescribeVpcClassicLink", + "ec2:DescribeAvailabilityZones", + "ec2:DescribeSubnets" + ], + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": "elasticloadbalancing:Describe*", + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": [ + "cloudwatch:ListMetrics", + "cloudwatch:GetMetricStatistics", + "cloudwatch:Describe*" + ], + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": "autoscaling:Describe*", + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": [ + "sns:ListSubscriptions", + "sns:ListTopics" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAI3A7GDXOYQV3VUQMK", + "PolicyName": "AutoScalingConsoleReadOnlyAccess", + "UpdateDate": "2017-01-12T19:48:53+00:00", + "VersionId": "v1" + }, + "AutoScalingFullAccess": { + "Arn": "arn:aws:iam::aws:policy/AutoScalingFullAccess", + "AttachmentCount": 0, + "CreateDate": "2017-01-12T19:31:58+00:00", + "DefaultVersionId": "v1", + "Document": { + "Statement": [ + { + "Action": "autoscaling:*", + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": "cloudwatch:PutMetricAlarm", + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAIAWRCSJDDXDXGPCFU", + "PolicyName": "AutoScalingFullAccess", + "UpdateDate": "2017-01-12T19:31:58+00:00", + "VersionId": "v1" + }, + "AutoScalingNotificationAccessRole": { + "Arn": "arn:aws:iam::aws:policy/service-role/AutoScalingNotificationAccessRole", + "AttachmentCount": 0, + "CreateDate": "2015-02-06T18:41:22+00:00", + "DefaultVersionId": "v1", + "Document": { + "Statement": [ + { + "Action": [ + "sqs:SendMessage", + "sqs:GetQueueUrl", + "sns:Publish" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/service-role/", + "PolicyId": "ANPAIO2VMUPGDC5PZVXVA", + "PolicyName": "AutoScalingNotificationAccessRole", + "UpdateDate": "2015-02-06T18:41:22+00:00", + "VersionId": "v1" + }, + "AutoScalingReadOnlyAccess": { + "Arn": "arn:aws:iam::aws:policy/AutoScalingReadOnlyAccess", + "AttachmentCount": 0, + "CreateDate": "2017-01-12T19:39:35+00:00", + "DefaultVersionId": "v1", + "Document": { + "Statement": [ + { + "Action": "autoscaling:Describe*", + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAIAFWUVLC2LPLSFTFG", + "PolicyName": "AutoScalingReadOnlyAccess", + "UpdateDate": "2017-01-12T19:39:35+00:00", + "VersionId": "v1" + }, + "Billing": { + "Arn": "arn:aws:iam::aws:policy/job-function/Billing", + "AttachmentCount": 0, + "CreateDate": "2016-11-10T17:33:18+00:00", + "DefaultVersionId": "v1", + "Document": { + "Statement": [ + { + "Action": [ + "aws-portal:*Billing", + "aws-portal:*Usage", + "aws-portal:*PaymentMethods", + "budgets:ViewBudget", + "budgets:ModifyBudget" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/job-function/", + "PolicyId": "ANPAIFTHXT6FFMIRT7ZEA", + "PolicyName": "Billing", + "UpdateDate": "2016-11-10T17:33:18+00:00", + "VersionId": "v1" + }, + "CloudFrontFullAccess": { + "Arn": "arn:aws:iam::aws:policy/CloudFrontFullAccess", + "AttachmentCount": 0, + "CreateDate": "2016-01-21T17:03:57+00:00", + "DefaultVersionId": "v3", + "Document": { + "Statement": [ + { + "Action": [ + "s3:ListAllMyBuckets" + ], + "Effect": "Allow", + "Resource": "arn:aws:s3:::*" + }, + { + "Action": [ + "acm:ListCertificates", + "cloudfront:*", + "iam:ListServerCertificates", + "waf:ListWebACLs", + "waf:GetWebACL" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAIPRV52SH6HDCCFY6U", + "PolicyName": "CloudFrontFullAccess", + "UpdateDate": "2016-01-21T17:03:57+00:00", + "VersionId": "v3" + }, + "CloudFrontReadOnlyAccess": { + "Arn": "arn:aws:iam::aws:policy/CloudFrontReadOnlyAccess", + "AttachmentCount": 0, + "CreateDate": "2016-01-21T17:03:28+00:00", + "DefaultVersionId": "v3", + "Document": { + "Statement": [ + { + "Action": [ + "acm:ListCertificates", + "cloudfront:Get*", + "cloudfront:List*", + "iam:ListServerCertificates", + "route53:List*", + "waf:ListWebACLs", + "waf:GetWebACL" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAJJZMNYOTZCNQP36LG", + "PolicyName": "CloudFrontReadOnlyAccess", + "UpdateDate": "2016-01-21T17:03:28+00:00", + "VersionId": "v3" + }, + "CloudSearchFullAccess": { + "Arn": "arn:aws:iam::aws:policy/CloudSearchFullAccess", + "AttachmentCount": 0, + "CreateDate": "2015-02-06T18:39:56+00:00", + "DefaultVersionId": "v1", + "Document": { + "Statement": [ + { + "Action": [ + "cloudsearch:*" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAIM6OOWKQ7L7VBOZOC", + "PolicyName": "CloudSearchFullAccess", + "UpdateDate": "2015-02-06T18:39:56+00:00", + "VersionId": "v1" + }, + "CloudSearchReadOnlyAccess": { + "Arn": "arn:aws:iam::aws:policy/CloudSearchReadOnlyAccess", + "AttachmentCount": 0, + "CreateDate": "2015-02-06T18:39:57+00:00", + "DefaultVersionId": "v1", + "Document": { + "Statement": [ + { + "Action": [ + "cloudsearch:Describe*", + "cloudsearch:List*" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAJWPLX7N7BCC3RZLHW", + "PolicyName": "CloudSearchReadOnlyAccess", + "UpdateDate": "2015-02-06T18:39:57+00:00", + "VersionId": "v1" + }, + "CloudWatchActionsEC2Access": { + "Arn": "arn:aws:iam::aws:policy/CloudWatchActionsEC2Access", + "AttachmentCount": 0, + "CreateDate": "2015-07-07T00:00:33+00:00", + "DefaultVersionId": "v1", + "Document": { + "Statement": [ + { + "Action": [ + "cloudwatch:Describe*", + "ec2:Describe*", + "ec2:RebootInstances", + "ec2:StopInstances", + "ec2:TerminateInstances" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAIOWD4E3FVSORSZTGU", + "PolicyName": "CloudWatchActionsEC2Access", + "UpdateDate": "2015-07-07T00:00:33+00:00", + "VersionId": "v1" + }, + "CloudWatchEventsBuiltInTargetExecutionAccess": { + "Arn": "arn:aws:iam::aws:policy/service-role/CloudWatchEventsBuiltInTargetExecutionAccess", + "AttachmentCount": 0, + "CreateDate": "2016-01-14T18:35:49+00:00", + "DefaultVersionId": "v1", + "Document": { + "Statement": [ + { + "Action": [ + "ec2:Describe*", + "ec2:RebootInstances", + "ec2:StopInstances", + "ec2:TerminateInstances", + "ec2:CreateSnapshot" + ], + "Effect": "Allow", + "Resource": "*", + "Sid": "CloudWatchEventsBuiltInTargetExecutionAccess" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/service-role/", + "PolicyId": "ANPAIC5AQ5DATYSNF4AUM", + "PolicyName": "CloudWatchEventsBuiltInTargetExecutionAccess", + "UpdateDate": "2016-01-14T18:35:49+00:00", + "VersionId": "v1" + }, + "CloudWatchEventsFullAccess": { + "Arn": "arn:aws:iam::aws:policy/CloudWatchEventsFullAccess", + "AttachmentCount": 0, + "CreateDate": "2016-01-14T18:37:08+00:00", + "DefaultVersionId": "v1", + "Document": { + "Statement": [ + { + "Action": "events:*", + "Effect": "Allow", + "Resource": "*", + "Sid": "CloudWatchEventsFullAccess" + }, + { + "Action": "iam:PassRole", + "Effect": "Allow", + "Resource": "arn:aws:iam::*:role/AWS_Events_Invoke_Targets", + "Sid": "IAMPassRoleForCloudWatchEvents" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAJZLOYLNHESMYOJAFU", + "PolicyName": "CloudWatchEventsFullAccess", + "UpdateDate": "2016-01-14T18:37:08+00:00", + "VersionId": "v1" + }, + "CloudWatchEventsInvocationAccess": { + "Arn": "arn:aws:iam::aws:policy/service-role/CloudWatchEventsInvocationAccess", + "AttachmentCount": 0, + "CreateDate": "2016-01-14T18:36:33+00:00", + "DefaultVersionId": "v1", + "Document": { + "Statement": [ + { + "Action": [ + "kinesis:PutRecord" + ], + "Effect": "Allow", + "Resource": "*", + "Sid": "CloudWatchEventsInvocationAccess" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/service-role/", + "PolicyId": "ANPAJJXD6JKJLK2WDLZNO", + "PolicyName": "CloudWatchEventsInvocationAccess", + "UpdateDate": "2016-01-14T18:36:33+00:00", + "VersionId": "v1" + }, + "CloudWatchEventsReadOnlyAccess": { + "Arn": "arn:aws:iam::aws:policy/CloudWatchEventsReadOnlyAccess", + "AttachmentCount": 0, + "CreateDate": "2017-08-10T17:25:34+00:00", + "DefaultVersionId": "v2", + "Document": { + "Statement": [ + { + "Action": [ + "events:DescribeRule", + "events:ListRuleNamesByTarget", + "events:ListRules", + "events:ListTargetsByRule", + "events:TestEventPattern", + "events:DescribeEventBus" + ], + "Effect": "Allow", + "Resource": "*", + "Sid": "CloudWatchEventsReadOnlyAccess" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAIILJPXXA6F7GYLYBS", + "PolicyName": "CloudWatchEventsReadOnlyAccess", + "UpdateDate": "2017-08-10T17:25:34+00:00", + "VersionId": "v2" + }, + "CloudWatchFullAccess": { + "Arn": "arn:aws:iam::aws:policy/CloudWatchFullAccess", + "AttachmentCount": 0, + "CreateDate": "2015-02-06T18:40:00+00:00", + "DefaultVersionId": "v1", + "Document": { + "Statement": [ + { + "Action": [ + "autoscaling:Describe*", + "cloudwatch:*", + "logs:*", + "sns:*" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAIKEABORKUXN6DEAZU", + "PolicyName": "CloudWatchFullAccess", + "UpdateDate": "2015-02-06T18:40:00+00:00", + "VersionId": "v1" + }, + "CloudWatchLogsFullAccess": { + "Arn": "arn:aws:iam::aws:policy/CloudWatchLogsFullAccess", + "AttachmentCount": 0, + "CreateDate": "2015-02-06T18:40:02+00:00", + "DefaultVersionId": "v1", + "Document": { + "Statement": [ + { + "Action": [ + "logs:*" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAJ3ZGNWK2R5HW5BQFO", + "PolicyName": "CloudWatchLogsFullAccess", + "UpdateDate": "2015-02-06T18:40:02+00:00", + "VersionId": "v1" + }, + "CloudWatchLogsReadOnlyAccess": { + "Arn": "arn:aws:iam::aws:policy/CloudWatchLogsReadOnlyAccess", + "AttachmentCount": 0, + "CreateDate": "2017-08-14T22:22:16+00:00", + "DefaultVersionId": "v3", + "Document": { + "Statement": [ + { + "Action": [ + "logs:Describe*", + "logs:Get*", + "logs:List*", + "logs:TestMetricFilter", + "logs:FilterLogEvents" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAJ2YIYDYSNNEHK3VKW", + "PolicyName": "CloudWatchLogsReadOnlyAccess", + "UpdateDate": "2017-08-14T22:22:16+00:00", + "VersionId": "v3" + }, + "CloudWatchReadOnlyAccess": { + "Arn": "arn:aws:iam::aws:policy/CloudWatchReadOnlyAccess", + "AttachmentCount": 0, + "CreateDate": "2015-02-06T18:40:01+00:00", + "DefaultVersionId": "v1", + "Document": { + "Statement": [ + { + "Action": [ + "autoscaling:Describe*", + "cloudwatch:Describe*", + "cloudwatch:Get*", + "cloudwatch:List*", + "logs:Get*", + "logs:Describe*", + "logs:TestMetricFilter", + "sns:Get*", + "sns:List*" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAJN23PDQP7SZQAE3QE", + "PolicyName": "CloudWatchReadOnlyAccess", + "UpdateDate": "2015-02-06T18:40:01+00:00", + "VersionId": "v1" + }, + "DataScientist": { + "Arn": "arn:aws:iam::aws:policy/job-function/DataScientist", + "AttachmentCount": 0, + "CreateDate": "2016-11-10T17:28:48+00:00", + "DefaultVersionId": "v1", + "Document": { + "Statement": [ + { + "Action": [ + "autoscaling:*", + "cloudwatch:*", + "cloudformation:CreateStack", + "cloudformation:DescribeStackEvents", + "datapipeline:Describe*", + "datapipeline:ListPipelines", + "datapipeline:GetPipelineDefinition", + "datapipeline:QueryObjects", + "dynamodb:*", + "ec2:CancelSpotInstanceRequests", + "ec2:CancelSpotFleetRequests", + "ec2:CreateTags", + "ec2:DeleteTags", + "ec2:Describe*", + "ec2:ModifyImageAttribute", + "ec2:ModifyInstanceAttribute", + "ec2:ModifySpotFleetRequest", + "ec2:RequestSpotInstances", + "ec2:RequestSpotFleet", + "elasticfilesystem:*", + "elasticmapreduce:*", + "es:*", + "firehose:*", + "iam:GetInstanceProfile", + "iam:GetRole", + "iam:GetPolicy", + "iam:GetPolicyVersion", + "iam:ListRoles", + "kinesis:*", + "kms:List*", + "lambda:Create*", + "lambda:Delete*", + "lambda:Get*", + "lambda:InvokeFunction", + "lambda:PublishVersion", + "lambda:Update*", + "lambda:List*", + "machinelearning:*", + "sdb:*", + "rds:*", + "sns:ListSubscriptions", + "sns:ListTopics", + "logs:DescribeLogStreams", + "logs:GetLogEvents", + "redshift:*", + "s3:CreateBucket", + "sns:CreateTopic", + "sns:Get*", + "sns:List*" + ], + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": [ + "s3:Abort*", + "s3:DeleteObject", + "s3:Get*", + "s3:List*", + "s3:PutAccelerateConfiguration", + "s3:PutBucketLogging", + "s3:PutBucketNotification", + "s3:PutBucketTagging", + "s3:PutObject", + "s3:Replicate*", + "s3:RestoreObject" + ], + "Effect": "Allow", + "Resource": [ + "*" + ] + }, + { + "Action": [ + "ec2:RunInstances", + "ec2:TerminateInstances" + ], + "Effect": "Allow", + "Resource": [ + "*" + ] + }, + { + "Action": [ + "iam:GetRole", + "iam:PassRole" + ], + "Effect": "Allow", + "Resource": [ + "arn:aws:iam::*:role/DataPipelineDefaultRole", + "arn:aws:iam::*:role/DataPipelineDefaultResourceRole", + "arn:aws:iam::*:role/EMR_EC2_DefaultRole", + "arn:aws:iam::*:role/EMR_DefaultRole", + "arn:aws:iam::*:role/kinesis-*" + ] + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/job-function/", + "PolicyId": "ANPAJ5YHI2BQW7EQFYDXS", + "PolicyName": "DataScientist", + "UpdateDate": "2016-11-10T17:28:48+00:00", + "VersionId": "v1" + }, + "DatabaseAdministrator": { + "Arn": "arn:aws:iam::aws:policy/job-function/DatabaseAdministrator", + "AttachmentCount": 0, + "CreateDate": "2016-11-10T17:25:43+00:00", + "DefaultVersionId": "v1", + "Document": { + "Statement": [ + { + "Action": [ + "cloudwatch:DeleteAlarms", + "cloudwatch:Describe*", + "cloudwatch:DisableAlarmActions", + "cloudwatch:EnableAlarmActions", + "cloudwatch:Get*", + "cloudwatch:List*", + "cloudwatch:PutMetricAlarm", + "datapipeline:ActivatePipeline", + "datapipeline:CreatePipeline", + "datapipeline:DeletePipeline", + "datapipeline:DescribeObjects", + "datapipeline:DescribePipelines", + "datapipeline:GetPipelineDefinition", + "datapipeline:ListPipelines", + "datapipeline:PutPipelineDefinition", + "datapipeline:QueryObjects", + "dynamodb:*", + "ec2:DescribeAccountAttributes", + "ec2:DescribeAddresses", + "ec2:DescribeAvailabilityZones", + "ec2:DescribeInternetGateways", + "ec2:DescribeSecurityGroups", + "ec2:DescribeSubnets", + "ec2:DescribeVpcs", + "elasticache:*", + "iam:ListRoles", + "iam:GetRole", + "kms:ListKeys", + "lambda:CreateEventSourceMapping", + "lambda:CreateFunction", + "lambda:DeleteEventSourceMapping", + "lambda:DeleteFunction", + "lambda:GetFunctionConfiguration", + "lambda:ListEventSourceMappings", + "lambda:ListFunctions", + "logs:DescribeLogGroups", + "logs:DescribeLogStreams", + "logs:FilterLogEvents", + "logs:GetLogEvents", + "logs:Create*", + "logs:PutLogEvents", + "logs:PutMetricFilter", + "rds:*", + "redshift:*", + "s3:CreateBucket", + "sns:CreateTopic", + "sns:DeleteTopic", + "sns:Get*", + "sns:List*", + "sns:SetTopicAttributes", + "sns:Subscribe", + "sns:Unsubscribe" + ], + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": [ + "s3:AbortMultipartUpload", + "s3:DeleteObject*", + "s3:Get*", + "s3:List*", + "s3:PutAccelerateConfiguration", + "s3:PutBucketTagging", + "s3:PutBucketVersioning", + "s3:PutBucketWebsite", + "s3:PutLifecycleConfiguration", + "s3:PutReplicationConfiguration", + "s3:PutObject*", + "s3:Replicate*", + "s3:RestoreObject" + ], + "Effect": "Allow", + "Resource": [ + "*" + ] + }, + { + "Action": [ + "iam:GetRole", + "iam:PassRole" + ], + "Effect": "Allow", + "Resource": [ + "arn:aws:iam::*:role/rds-monitoring-role", + "arn:aws:iam::*:role/rdbms-lambda-access", + "arn:aws:iam::*:role/lambda_exec_role", + "arn:aws:iam::*:role/lambda-dynamodb-*", + "arn:aws:iam::*:role/lambda-vpc-execution-role", + "arn:aws:iam::*:role/DataPipelineDefaultRole", + "arn:aws:iam::*:role/DataPipelineDefaultResourceRole" + ] + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/job-function/", + "PolicyId": "ANPAIGBMAW4VUQKOQNVT6", + "PolicyName": "DatabaseAdministrator", + "UpdateDate": "2016-11-10T17:25:43+00:00", + "VersionId": "v1" + }, + "IAMFullAccess": { + "Arn": "arn:aws:iam::aws:policy/IAMFullAccess", + "AttachmentCount": 2, + "CreateDate": "2015-02-06T18:40:38+00:00", + "DefaultVersionId": "v1", + "Document": { + "Statement": [ + { + "Action": "iam:*", + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAI7XKCFMBPM3QQRRVQ", + "PolicyName": "IAMFullAccess", + "UpdateDate": "2015-02-06T18:40:38+00:00", + "VersionId": "v1" + }, + "IAMReadOnlyAccess": { + "Arn": "arn:aws:iam::aws:policy/IAMReadOnlyAccess", + "AttachmentCount": 0, + "CreateDate": "2016-09-06T17:06:37+00:00", + "DefaultVersionId": "v3", + "Document": { + "Statement": [ + { + "Action": [ + "iam:GenerateCredentialReport", + "iam:GenerateServiceLastAccessedDetails", + "iam:Get*", + "iam:List*" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAJKSO7NDY4T57MWDSQ", + "PolicyName": "IAMReadOnlyAccess", + "UpdateDate": "2016-09-06T17:06:37+00:00", + "VersionId": "v3" + }, + "IAMSelfManageServiceSpecificCredentials": { + "Arn": "arn:aws:iam::aws:policy/IAMSelfManageServiceSpecificCredentials", + "AttachmentCount": 0, + "CreateDate": "2016-12-22T17:25:18+00:00", + "DefaultVersionId": "v1", + "Document": { + "Statement": [ + { + "Action": [ + "iam:CreateServiceSpecificCredential", + "iam:ListServiceSpecificCredentials", + "iam:UpdateServiceSpecificCredential", + "iam:DeleteServiceSpecificCredential", + "iam:ResetServiceSpecificCredential" + ], + "Effect": "Allow", + "Resource": "arn:aws:iam::*:user/${aws:username}" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAI4VT74EMXK2PMQJM2", + "PolicyName": "IAMSelfManageServiceSpecificCredentials", + "UpdateDate": "2016-12-22T17:25:18+00:00", + "VersionId": "v1" + }, + "IAMUserChangePassword": { + "Arn": "arn:aws:iam::aws:policy/IAMUserChangePassword", + "AttachmentCount": 1, + "CreateDate": "2016-11-15T23:18:55+00:00", + "DefaultVersionId": "v2", + "Document": { + "Statement": [ + { + "Action": [ + "iam:ChangePassword" + ], + "Effect": "Allow", + "Resource": [ + "arn:aws:iam::*:user/${aws:username}" + ] + }, + { + "Action": [ + "iam:GetAccountPasswordPolicy" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAJ4L4MM2A7QIEB56MS", + "PolicyName": "IAMUserChangePassword", + "UpdateDate": "2016-11-15T23:18:55+00:00", + "VersionId": "v2" + }, + "IAMUserSSHKeys": { + "Arn": "arn:aws:iam::aws:policy/IAMUserSSHKeys", + "AttachmentCount": 1, + "CreateDate": "2015-07-09T17:08:54+00:00", + "DefaultVersionId": "v1", + "Document": { + "Statement": [ + { + "Action": [ + "iam:DeleteSSHPublicKey", + "iam:GetSSHPublicKey", + "iam:ListSSHPublicKeys", + "iam:UpdateSSHPublicKey", + "iam:UploadSSHPublicKey" + ], + "Effect": "Allow", + "Resource": "arn:aws:iam::*:user/${aws:username}" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAJTSHUA4UXGXU7ANUA", + "PolicyName": "IAMUserSSHKeys", + "UpdateDate": "2015-07-09T17:08:54+00:00", + "VersionId": "v1" + }, + "NetworkAdministrator": { + "Arn": "arn:aws:iam::aws:policy/job-function/NetworkAdministrator", + "AttachmentCount": 0, + "CreateDate": "2017-03-20T18:44:58+00:00", + "DefaultVersionId": "v2", + "Document": { + "Statement": [ + { + "Action": [ + "autoscaling:Describe*", + "ec2:AllocateAddress", + "ec2:AssignPrivateIpAddresses", + "ec2:AssociateAddress", + "ec2:AssociateDhcpOptions", + "ec2:AssociateRouteTable", + "ec2:AttachInternetGateway", + "ec2:AttachNetworkInterface", + "ec2:AttachVpnGateway", + "ec2:CreateCustomerGateway", + "ec2:CreateDhcpOptions", + "ec2:CreateFlowLogs", + "ec2:CreateInternetGateway", + "ec2:CreateNatGateway", + "ec2:CreateNetworkAcl", + "ec2:CreateNetworkAcl", + "ec2:CreateNetworkAclEntry", + "ec2:CreateNetworkInterface", + "ec2:CreateRoute", + "ec2:CreateRouteTable", + "ec2:CreateSecurityGroup", + "ec2:CreateSubnet", + "ec2:CreateTags", + "ec2:CreateVpc", + "ec2:CreateVpcEndpoint", + "ec2:CreateVpnConnection", + "ec2:CreateVpnConnectionRoute", + "ec2:CreateVpnGateway", + "ec2:CreatePlacementGroup", + "ec2:DeletePlacementGroup", + "ec2:DescribePlacementGroups", + "ec2:DeleteFlowLogs", + "ec2:DeleteNatGateway", + "ec2:DeleteNetworkInterface", + "ec2:DeleteSubnet", + "ec2:DeleteTags", + "ec2:DeleteVpc", + "ec2:DeleteVpcEndpoints", + "ec2:DeleteVpnConnection", + "ec2:DeleteVpnConnectionRoute", + "ec2:DeleteVpnGateway", + "ec2:DescribeAddresses", + "ec2:DescribeAvailabilityZones", + "ec2:DescribeClassicLinkInstances", + "ec2:DescribeCustomerGateways", + "ec2:DescribeVpcClassicLinkDnsSupport", + "ec2:DescribeDhcpOptions", + "ec2:DescribeFlowLogs", + "ec2:DescribeInstances", + "ec2:DescribeInternetGateways", + "ec2:DescribeKeyPairs", + "ec2:DescribeMovingAddresses", + "ec2:DescribeNatGateways", + "ec2:DescribeNetworkAcls", + "ec2:DescribeNetworkInterfaceAttribute", + "ec2:DescribeNetworkInterfaces", + "ec2:DescribePrefixLists", + "ec2:DescribeRouteTables", + "ec2:DescribeSecurityGroups", + "ec2:DescribeSubnets", + "ec2:DescribeTags", + "ec2:DescribeVpcAttribute", + "ec2:DescribeVpcClassicLink", + "ec2:DescribeVpcEndpoints", + "ec2:DescribeVpcEndpointServices", + "ec2:DescribeVpcPeeringConnections", + "ec2:DescribeVpcs", + "ec2:DescribeVpnConnections", + "ec2:DescribeVpnGateways", + "ec2:DetachInternetGateway", + "ec2:DetachNetworkInterface", + "ec2:DetachVpnGateway", + "ec2:DisableVgwRoutePropagation", + "ec2:DisassociateAddress", + "ec2:DisassociateRouteTable", + "ec2:EnableVgwRoutePropagation", + "ec2:ModifyNetworkInterfaceAttribute", + "ec2:ModifySubnetAttribute", + "ec2:ModifyVpcAttribute", + "ec2:ModifyVpcEndpoint", + "ec2:MoveAddressToVpc", + "ec2:ReleaseAddress", + "ec2:ReplaceNetworkAclAssociation", + "ec2:ReplaceNetworkAclEntry", + "ec2:ReplaceRoute", + "ec2:ReplaceRouteTableAssociation", + "ec2:ResetNetworkInterfaceAttribute", + "ec2:RestoreAddressToClassic", + "ec2:UnassignPrivateIpAddresses", + "directconnect:*", + "route53:*", + "route53domains:*", + "cloudfront:ListDistributions", + "elasticloadbalancing:*", + "elasticbeanstalk:Describe*", + "elasticbeanstalk:List*", + "elasticbeanstalk:RetrieveEnvironmentInfo", + "elasticbeanstalk:RequestEnvironmentInfo", + "sns:ListTopics", + "sns:ListSubscriptionsByTopic", + "sns:CreateTopic", + "cloudwatch:DescribeAlarms", + "cloudwatch:PutMetricAlarm", + "cloudwatch:DeleteAlarms", + "cloudwatch:GetMetricStatistics", + "logs:DescribeLogGroups", + "logs:DescribeLogStreams", + "logs:GetLogEvents" + ], + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": [ + "ec2:AcceptVpcPeeringConnection", + "ec2:AttachClassicLinkVpc", + "ec2:AuthorizeSecurityGroupEgress", + "ec2:AuthorizeSecurityGroupIngress", + "ec2:CreateVpcPeeringConnection", + "ec2:DeleteCustomerGateway", + "ec2:DeleteDhcpOptions", + "ec2:DeleteInternetGateway", + "ec2:DeleteNetworkAcl", + "ec2:DeleteNetworkAclEntry", + "ec2:DeleteRoute", + "ec2:DeleteRouteTable", + "ec2:DeleteSecurityGroup", + "ec2:DeleteVolume", + "ec2:DeleteVpcPeeringConnection", + "ec2:DetachClassicLinkVpc", + "ec2:DisableVpcClassicLink", + "ec2:EnableVpcClassicLink", + "ec2:GetConsoleScreenshot", + "ec2:RejectVpcPeeringConnection", + "ec2:RevokeSecurityGroupEgress", + "ec2:RevokeSecurityGroupIngress" + ], + "Effect": "Allow", + "Resource": [ + "*" + ] + }, + { + "Action": [ + "s3:ListBucket", + "s3:GetBucketLocation", + "s3:GetBucketWebsiteConfiguration" + ], + "Effect": "Allow", + "Resource": [ + "*" + ] + }, + { + "Action": [ + "iam:GetRole", + "iam:ListRoles", + "iam:PassRole" + ], + "Effect": "Allow", + "Resource": "arn:aws:iam::*:role/flow-logs-*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/job-function/", + "PolicyId": "ANPAJPNMADZFJCVPJVZA2", + "PolicyName": "NetworkAdministrator", + "UpdateDate": "2017-03-20T18:44:58+00:00", + "VersionId": "v2" + }, + "PowerUserAccess": { + "Arn": "arn:aws:iam::aws:policy/PowerUserAccess", + "AttachmentCount": 0, + "CreateDate": "2016-12-06T18:11:16+00:00", + "DefaultVersionId": "v2", + "Document": { + "Statement": [ + { + "Effect": "Allow", + "NotAction": [ + "iam:*", + "organizations:*" + ], + "Resource": "*" + }, + { + "Action": "organizations:DescribeOrganization", + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAJYRXTHIB4FOVS3ZXS", + "PolicyName": "PowerUserAccess", + "UpdateDate": "2016-12-06T18:11:16+00:00", + "VersionId": "v2" + }, + "QuickSightAccessForS3StorageManagementAnalyticsReadOnly": { + "Arn": "arn:aws:iam::aws:policy/service-role/QuickSightAccessForS3StorageManagementAnalyticsReadOnly", + "AttachmentCount": 0, + "CreateDate": "2017-07-21T00:02:14+00:00", + "DefaultVersionId": "v3", + "Document": { + "Statement": [ + { + "Action": [ + "s3:GetObject", + "s3:GetObjectMetadata" + ], + "Effect": "Allow", + "Resource": [ + "arn:aws:s3:::s3-analytics-export-shared-*" + ] + }, + { + "Action": [ + "s3:GetAnalyticsConfiguration", + "s3:ListAllMyBuckets", + "s3:GetBucketLocation" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/service-role/", + "PolicyId": "ANPAIFWG3L3WDMR4I7ZJW", + "PolicyName": "QuickSightAccessForS3StorageManagementAnalyticsReadOnly", + "UpdateDate": "2017-07-21T00:02:14+00:00", + "VersionId": "v3" + }, + "RDSCloudHsmAuthorizationRole": { + "Arn": "arn:aws:iam::aws:policy/service-role/RDSCloudHsmAuthorizationRole", + "AttachmentCount": 0, + "CreateDate": "2015-02-06T18:41:29+00:00", + "DefaultVersionId": "v1", + "Document": { + "Statement": [ + { + "Action": [ + "cloudhsm:CreateLunaClient", + "cloudhsm:GetClientConfiguration", + "cloudhsm:DeleteLunaClient", + "cloudhsm:DescribeLunaClient", + "cloudhsm:ModifyLunaClient", + "cloudhsm:DescribeHapg", + "cloudhsm:ModifyHapg", + "cloudhsm:GetConfig" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/service-role/", + "PolicyId": "ANPAIWKFXRLQG2ROKKXLE", + "PolicyName": "RDSCloudHsmAuthorizationRole", + "UpdateDate": "2015-02-06T18:41:29+00:00", + "VersionId": "v1" + }, + "ReadOnlyAccess": { + "Arn": "arn:aws:iam::aws:policy/ReadOnlyAccess", + "AttachmentCount": 0, + "CreateDate": "2017-07-20T17:43:06+00:00", + "DefaultVersionId": "v29", + "Document": { + "Statement": [ + { + "Action": [ + "acm:Describe*", + "acm:Get*", + "acm:List*", + "apigateway:GET", + "application-autoscaling:Describe*", + "appstream:Describe*", + "appstream:Get*", + "appstream:List*", + "athena:List*", + "athena:Batch*", + "athena:Get*", + "autoscaling:Describe*", + "batch:List*", + "batch:Describe*", + "clouddirectory:List*", + "clouddirectory:BatchRead", + "clouddirectory:Get*", + "clouddirectory:LookupPolicy", + "cloudformation:Describe*", + "cloudformation:Get*", + "cloudformation:List*", + "cloudformation:Estimate*", + "cloudformation:Preview*", + "cloudfront:Get*", + "cloudfront:List*", + "cloudhsm:List*", + "cloudhsm:Describe*", + "cloudhsm:Get*", + "cloudsearch:Describe*", + "cloudsearch:List*", + "cloudtrail:Describe*", + "cloudtrail:Get*", + "cloudtrail:List*", + "cloudtrail:LookupEvents", + "cloudwatch:Describe*", + "cloudwatch:Get*", + "cloudwatch:List*", + "codebuild:BatchGet*", + "codebuild:List*", + "codecommit:BatchGet*", + "codecommit:Get*", + "codecommit:GitPull", + "codecommit:List*", + "codedeploy:BatchGet*", + "codedeploy:Get*", + "codedeploy:List*", + "codepipeline:List*", + "codepipeline:Get*", + "codestar:List*", + "codestar:Describe*", + "codestar:Get*", + "codestar:Verify*", + "cognito-identity:List*", + "cognito-identity:Describe*", + "cognito-identity:Lookup*", + "cognito-sync:List*", + "cognito-sync:Describe*", + "cognito-sync:Get*", + "cognito-sync:QueryRecords", + "cognito-idp:AdminList*", + "cognito-idp:List*", + "cognito-idp:Describe*", + "cognito-idp:Get*", + "config:Deliver*", + "config:Describe*", + "config:Get*", + "config:List*", + "connect:List*", + "connect:Describe*", + "connect:Get*", + "datapipeline:Describe*", + "datapipeline:EvaluateExpression", + "datapipeline:Get*", + "datapipeline:List*", + "datapipeline:QueryObjects", + "datapipeline:Validate*", + "directconnect:Describe*", + "directconnect:Confirm*", + "devicefarm:List*", + "devicefarm:Get*", + "discovery:Describe*", + "discovery:List*", + "discovery:Get*", + "dms:Describe*", + "dms:List*", + "dms:Test*", + "ds:Check*", + "ds:Describe*", + "ds:Get*", + "ds:List*", + "ds:Verify*", + "dynamodb:BatchGet*", + "dynamodb:Describe*", + "dynamodb:Get*", + "dynamodb:List*", + "dynamodb:Query", + "dynamodb:Scan", + "ec2:Describe*", + "ec2:Get*", + "ec2messages:Get*", + "ecr:BatchCheck*", + "ecr:BatchGet*", + "ecr:Describe*", + "ecr:Get*", + "ecr:List*", + "ecs:Describe*", + "ecs:List*", + "elasticache:Describe*", + "elasticache:List*", + "elasticbeanstalk:Check*", + "elasticbeanstalk:Describe*", + "elasticbeanstalk:List*", + "elasticbeanstalk:Request*", + "elasticbeanstalk:Retrieve*", + "elasticbeanstalk:Validate*", + "elasticfilesystem:Describe*", + "elasticloadbalancing:Describe*", + "elasticmapreduce:Describe*", + "elasticmapreduce:List*", + "elasticmapreduce:View*", + "elastictranscoder:List*", + "elastictranscoder:Read*", + "es:Describe*", + "es:List*", + "es:ESHttpGet", + "es:ESHttpHead", + "events:Describe*", + "events:List*", + "events:Test*", + "firehose:Describe*", + "firehose:List*", + "gamelift:List*", + "gamelift:Get*", + "gamelift:Describe*", + "gamelift:RequestUploadCredentials", + "gamelift:ResolveAlias", + "gamelift:Search*", + "glacier:List*", + "glacier:Describe*", + "glacier:Get*", + "health:Describe*", + "health:Get*", + "health:List*", + "iam:Generate*", + "iam:Get*", + "iam:List*", + "iam:Simulate*", + "importexport:Get*", + "importexport:List*", + "inspector:Describe*", + "inspector:Get*", + "inspector:List*", + "inspector:Preview*", + "inspector:LocalizeText", + "iot:Describe*", + "iot:Get*", + "iot:List*", + "kinesisanalytics:Describe*", + "kinesisanalytics:Discover*", + "kinesisanalytics:Get*", + "kinesisanalytics:List*", + "kinesis:Describe*", + "kinesis:Get*", + "kinesis:List*", + "kms:Describe*", + "kms:Get*", + "kms:List*", + "lambda:List*", + "lambda:Get*", + "lex:Get*", + "lightsail:Get*", + "lightsail:Is*", + "lightsail:Download*", + "logs:Describe*", + "logs:Get*", + "logs:FilterLogEvents", + "logs:ListTagsLogGroup", + "logs:TestMetricFilter", + "machinelearning:Describe*", + "machinelearning:Get*", + "mobileanalytics:Get*", + "mobilehub:Get*", + "mobilehub:List*", + "mobilehub:Validate*", + "mobilehub:Verify*", + "mobiletargeting:Get*", + "opsworks:Describe*", + "opsworks:Get*", + "opsworks-cm:Describe*", + "organizations:Describe*", + "organizations:List*", + "polly:Describe*", + "polly:Get*", + "polly:List*", + "polly:SynthesizeSpeech", + "rekognition:CompareFaces", + "rekognition:Detect*", + "rekognition:List*", + "rekognition:Search*", + "rds:Describe*", + "rds:List*", + "rds:Download*", + "redshift:Describe*", + "redshift:View*", + "redshift:Get*", + "route53:Get*", + "route53:List*", + "route53:Test*", + "route53domains:Check*", + "route53domains:Get*", + "route53domains:List*", + "route53domains:View*", + "s3:Get*", + "s3:List*", + "s3:Head*", + "sdb:Get*", + "sdb:List*", + "sdb:Select*", + "servicecatalog:List*", + "servicecatalog:Scan*", + "servicecatalog:Search*", + "servicecatalog:Describe*", + "ses:Get*", + "ses:List*", + "ses:Describe*", + "ses:Verify*", + "shield:Describe*", + "shield:List*", + "sns:Get*", + "sns:List*", + "sns:Check*", + "sqs:Get*", + "sqs:List*", + "sqs:Receive*", + "ssm:Describe*", + "ssm:Get*", + "ssm:List*", + "states:List*", + "states:Describe*", + "states:GetExecutionHistory", + "storagegateway:Describe*", + "storagegateway:List*", + "sts:Get*", + "swf:Count*", + "swf:Describe*", + "swf:Get*", + "swf:List*", + "tag:Get*", + "trustedadvisor:Describe*", + "waf:Get*", + "waf:List*", + "waf-regional:List*", + "waf-regional:Get*", + "workdocs:Describe*", + "workdocs:Get*", + "workdocs:CheckAlias", + "workmail:Describe*", + "workmail:Get*", + "workmail:List*", + "workmail:Search*", + "workspaces:Describe*", + "xray:BatchGet*", + "xray:Get*" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAILL3HVNFSB6DCOWYQ", + "PolicyName": "ReadOnlyAccess", + "UpdateDate": "2017-07-20T17:43:06+00:00", + "VersionId": "v29" + }, + "ResourceGroupsandTagEditorFullAccess": { + "Arn": "arn:aws:iam::aws:policy/ResourceGroupsandTagEditorFullAccess", + "AttachmentCount": 0, + "CreateDate": "2015-02-06T18:39:53+00:00", + "DefaultVersionId": "v1", + "Document": { + "Statement": [ + { + "Action": [ + "tag:getResources", + "tag:getTagKeys", + "tag:getTagValues", + "tag:addResourceTags", + "tag:removeResourceTags" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAJNOS54ZFXN4T2Y34A", + "PolicyName": "ResourceGroupsandTagEditorFullAccess", + "UpdateDate": "2015-02-06T18:39:53+00:00", + "VersionId": "v1" + }, + "ResourceGroupsandTagEditorReadOnlyAccess": { + "Arn": "arn:aws:iam::aws:policy/ResourceGroupsandTagEditorReadOnlyAccess", + "AttachmentCount": 0, + "CreateDate": "2015-02-06T18:39:54+00:00", + "DefaultVersionId": "v1", + "Document": { + "Statement": [ + { + "Action": [ + "tag:getResources", + "tag:getTagKeys", + "tag:getTagValues" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAJHXQTPI5I5JKAIU74", + "PolicyName": "ResourceGroupsandTagEditorReadOnlyAccess", + "UpdateDate": "2015-02-06T18:39:54+00:00", + "VersionId": "v1" + }, + "SecurityAudit": { + "Arn": "arn:aws:iam::aws:policy/SecurityAudit", + "AttachmentCount": 0, + "CreateDate": "2017-07-12T20:16:44+00:00", + "DefaultVersionId": "v12", + "Document": { + "Statement": [ + { + "Action": [ + "acm:ListCertificates", + "acm:DescribeCertificate", + "cloudformation:getStackPolicy", + "logs:describeLogGroups", + "logs:describeMetricFilters", + "autoscaling:Describe*", + "cloudformation:DescribeStack*", + "cloudformation:GetTemplate", + "cloudformation:ListStack*", + "cloudfront:Get*", + "cloudfront:List*", + "cloudtrail:DescribeTrails", + "cloudtrail:GetTrailStatus", + "cloudtrail:ListTags", + "cloudwatch:Describe*", + "codecommit:BatchGetRepositories", + "codecommit:GetBranch", + "codecommit:GetObjectIdentifier", + "codecommit:GetRepository", + "codecommit:List*", + "codedeploy:Batch*", + "codedeploy:Get*", + "codedeploy:List*", + "config:Deliver*", + "config:Describe*", + "config:Get*", + "datapipeline:DescribeObjects", + "datapipeline:DescribePipelines", + "datapipeline:EvaluateExpression", + "datapipeline:GetPipelineDefinition", + "datapipeline:ListPipelines", + "datapipeline:QueryObjects", + "datapipeline:ValidatePipelineDefinition", + "directconnect:Describe*", + "dynamodb:ListTables", + "ec2:Describe*", + "ecs:Describe*", + "ecs:List*", + "elasticache:Describe*", + "elasticbeanstalk:Describe*", + "elasticloadbalancing:Describe*", + "elasticmapreduce:DescribeJobFlows", + "elasticmapreduce:ListClusters", + "elasticmapreduce:ListInstances", + "es:ListDomainNames", + "es:Describe*", + "firehose:Describe*", + "firehose:List*", + "glacier:DescribeVault", + "glacier:GetVaultAccessPolicy", + "glacier:ListVaults", + "iam:GenerateCredentialReport", + "iam:Get*", + "iam:List*", + "kms:Describe*", + "kms:Get*", + "kms:List*", + "lambda:GetPolicy", + "lambda:ListFunctions", + "rds:Describe*", + "rds:DownloadDBLogFilePortion", + "rds:ListTagsForResource", + "redshift:Describe*", + "route53:GetChange", + "route53:GetCheckerIpRanges", + "route53:GetGeoLocation", + "route53:GetHealthCheck", + "route53:GetHealthCheckCount", + "route53:GetHealthCheckLastFailureReason", + "route53:GetHostedZone", + "route53:GetHostedZoneCount", + "route53:GetReusableDelegationSet", + "route53:ListGeoLocations", + "route53:ListHealthChecks", + "route53:ListHostedZones", + "route53:ListHostedZonesByName", + "route53:ListResourceRecordSets", + "route53:ListReusableDelegationSets", + "route53:ListTagsForResource", + "route53:ListTagsForResources", + "route53domains:GetDomainDetail", + "route53domains:GetOperationDetail", + "route53domains:ListDomains", + "route53domains:ListOperations", + "route53domains:ListTagsForDomain", + "s3:GetBucket*", + "s3:GetAccelerateConfiguration", + "s3:GetAnalyticsConfiguration", + "s3:GetInventoryConfiguration", + "s3:GetMetricsConfiguration", + "s3:GetReplicationConfiguration", + "s3:GetLifecycleConfiguration", + "s3:GetObjectAcl", + "s3:GetObjectVersionAcl", + "s3:ListAllMyBuckets", + "sdb:DomainMetadata", + "sdb:ListDomains", + "ses:GetIdentityDkimAttributes", + "ses:GetIdentityVerificationAttributes", + "ses:ListIdentities", + "sns:GetTopicAttributes", + "sns:ListSubscriptionsByTopic", + "sns:ListTopics", + "sqs:GetQueueAttributes", + "sqs:ListQueues", + "tag:GetResources", + "tag:GetTagKeys" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAIX2T3QCXHR2OGGCTO", + "PolicyName": "SecurityAudit", + "UpdateDate": "2017-07-12T20:16:44+00:00", + "VersionId": "v12" + }, + "ServerMigrationConnector": { + "Arn": "arn:aws:iam::aws:policy/ServerMigrationConnector", + "AttachmentCount": 0, + "CreateDate": "2016-10-24T21:45:56+00:00", + "DefaultVersionId": "v1", + "Document": { + "Statement": [ + { + "Action": "iam:GetUser", + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": [ + "sms:SendMessage", + "sms:GetMessages" + ], + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": [ + "s3:CreateBucket", + "s3:DeleteBucket", + "s3:DeleteObject", + "s3:GetBucketLocation", + "s3:GetObject", + "s3:ListBucket", + "s3:PutObject", + "s3:PutObjectAcl", + "s3:PutLifecycleConfiguration", + "s3:AbortMultipartUpload", + "s3:ListBucketMultipartUploads", + "s3:ListMultipartUploadParts" + ], + "Effect": "Allow", + "Resource": [ + "arn:aws:s3:::sms-b-*", + "arn:aws:s3:::import-to-ec2-*", + "arn:aws:s3:::server-migration-service-upgrade", + "arn:aws:s3:::server-migration-service-upgrade/*", + "arn:aws:s3:::connector-platform-upgrade-info/*", + "arn:aws:s3:::connector-platform-upgrade-info", + "arn:aws:s3:::connector-platform-upgrade-bundles/*", + "arn:aws:s3:::connector-platform-upgrade-bundles", + "arn:aws:s3:::connector-platform-release-notes/*", + "arn:aws:s3:::connector-platform-release-notes" + ] + }, + { + "Action": "awsconnector:*", + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": [ + "SNS:Publish" + ], + "Effect": "Allow", + "Resource": "arn:aws:sns:*:*:metrics-sns-topic-for-*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAJKZRWXIPK5HSG3QDQ", + "PolicyName": "ServerMigrationConnector", + "UpdateDate": "2016-10-24T21:45:56+00:00", + "VersionId": "v1" + }, + "ServerMigrationServiceRole": { + "Arn": "arn:aws:iam::aws:policy/service-role/ServerMigrationServiceRole", + "AttachmentCount": 0, + "CreateDate": "2017-06-16T18:02:04+00:00", + "DefaultVersionId": "v2", + "Document": { + "Statement": [ + { + "Action": [ + "ec2:ModifySnapshotAttribute", + "ec2:CopySnapshot", + "ec2:CopyImage", + "ec2:Describe*", + "ec2:DeleteSnapshot", + "ec2:DeregisterImage", + "ec2:CreateTags", + "ec2:DeleteTags" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/service-role/", + "PolicyId": "ANPAJMBH3M6BO63XFW2D4", + "PolicyName": "ServerMigrationServiceRole", + "UpdateDate": "2017-06-16T18:02:04+00:00", + "VersionId": "v2" + }, + "ServiceCatalogAdminFullAccess": { + "Arn": "arn:aws:iam::aws:policy/ServiceCatalogAdminFullAccess", + "AttachmentCount": 0, + "CreateDate": "2016-11-11T18:40:24+00:00", + "DefaultVersionId": "v2", + "Document": { + "Statement": [ + { + "Action": [ + "catalog-admin:*", + "catalog-user:*", + "cloudformation:CreateStack", + "cloudformation:CreateUploadBucket", + "cloudformation:DeleteStack", + "cloudformation:DescribeStackEvents", + "cloudformation:DescribeStacks", + "cloudformation:GetTemplateSummary", + "cloudformation:SetStackPolicy", + "cloudformation:ValidateTemplate", + "cloudformation:UpdateStack", + "iam:GetGroup", + "iam:GetRole", + "iam:GetUser", + "iam:ListGroups", + "iam:ListRoles", + "iam:ListUsers", + "iam:PassRole", + "s3:CreateBucket", + "s3:GetObject", + "s3:PutObject", + "servicecatalog:*" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAIKTX42IAS75B7B7BY", + "PolicyName": "ServiceCatalogAdminFullAccess", + "UpdateDate": "2016-11-11T18:40:24+00:00", + "VersionId": "v2" + }, + "ServiceCatalogAdminReadOnlyAccess": { + "Arn": "arn:aws:iam::aws:policy/ServiceCatalogAdminReadOnlyAccess", + "AttachmentCount": 0, + "CreateDate": "2017-08-08T18:57:36+00:00", + "DefaultVersionId": "v5", + "Document": { + "Statement": [ + { + "Action": [ + "catalog-admin:DescribeConstraints", + "catalog-admin:DescribeListingForProduct", + "catalog-admin:DescribeListings", + "catalog-admin:DescribePortfolios", + "catalog-admin:DescribeProductVersions", + "catalog-admin:GetPortfolioCount", + "catalog-admin:GetPortfolios", + "catalog-admin:GetProductCounts", + "catalog-admin:ListAllPortfolioConstraints", + "catalog-admin:ListPortfolioConstraints", + "catalog-admin:ListPortfolios", + "catalog-admin:ListPrincipalConstraints", + "catalog-admin:ListProductConstraints", + "catalog-admin:ListResourceUsers", + "catalog-admin:ListTagsForResource", + "catalog-admin:SearchListings", + "catalog-user:*", + "cloudformation:DescribeStackEvents", + "cloudformation:DescribeStacks", + "cloudformation:GetTemplateSummary", + "iam:GetGroup", + "iam:GetRole", + "iam:GetUser", + "iam:ListGroups", + "iam:ListRoles", + "iam:ListUsers", + "s3:GetObject", + "servicecatalog:DescribeTagOption", + "servicecatalog:GetTagOptionMigrationStatus", + "servicecatalog:ListResourcesForTagOption", + "servicecatalog:ListTagOptions", + "servicecatalog:AccountLevelDescribeRecord", + "servicecatalog:AccountLevelListRecordHistory", + "servicecatalog:AccountLevelScanProvisionedProducts", + "servicecatalog:DescribeProduct", + "servicecatalog:DescribeProductView", + "servicecatalog:DescribeProvisioningParameters", + "servicecatalog:DescribeProvisionedProduct", + "servicecatalog:DescribeRecord", + "servicecatalog:ListLaunchPaths", + "servicecatalog:ListRecordHistory", + "servicecatalog:ScanProvisionedProducts", + "servicecatalog:SearchProducts", + "servicecatalog:DescribeConstraint", + "servicecatalog:DescribeProductAsAdmin", + "servicecatalog:DescribePortfolio", + "servicecatalog:DescribeProvisioningArtifact", + "servicecatalog:ListAcceptedPortfolioShares", + "servicecatalog:ListConstraintsForPortfolio", + "servicecatalog:ListPortfolioAccess", + "servicecatalog:ListPortfolios", + "servicecatalog:ListPortfoliosForProduct", + "servicecatalog:ListPrincipalsForPortfolio", + "servicecatalog:ListProvisioningArtifacts", + "servicecatalog:SearchProductsAsAdmin" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAJ7XOUSS75M4LIPKO4", + "PolicyName": "ServiceCatalogAdminReadOnlyAccess", + "UpdateDate": "2017-08-08T18:57:36+00:00", + "VersionId": "v5" + }, + "ServiceCatalogEndUserAccess": { + "Arn": "arn:aws:iam::aws:policy/ServiceCatalogEndUserAccess", + "AttachmentCount": 0, + "CreateDate": "2017-08-08T18:58:57+00:00", + "DefaultVersionId": "v4", + "Document": { + "Statement": [ + { + "Action": [ + "catalog-user:*", + "s3:GetObject", + "servicecatalog:DescribeProduct", + "servicecatalog:DescribeProductView", + "servicecatalog:DescribeProvisioningParameters", + "servicecatalog:ListLaunchPaths", + "servicecatalog:SearchProducts" + ], + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": [ + "servicecatalog:ListRecordHistory", + "servicecatalog:DescribeProvisionedProduct", + "servicecatalog:DescribeRecord", + "servicecatalog:ScanProvisionedProducts" + ], + "Condition": { + "StringEquals": { + "servicecatalog:userLevel": "self" + } + }, + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAJ56OMCO72RI4J5FSA", + "PolicyName": "ServiceCatalogEndUserAccess", + "UpdateDate": "2017-08-08T18:58:57+00:00", + "VersionId": "v4" + }, + "ServiceCatalogEndUserFullAccess": { + "Arn": "arn:aws:iam::aws:policy/ServiceCatalogEndUserFullAccess", + "AttachmentCount": 0, + "CreateDate": "2017-08-08T18:58:54+00:00", + "DefaultVersionId": "v4", + "Document": { + "Statement": [ + { + "Action": [ + "catalog-user:*", + "cloudformation:CreateStack", + "cloudformation:DeleteStack", + "cloudformation:DescribeStackEvents", + "cloudformation:DescribeStacks", + "cloudformation:GetTemplateSummary", + "cloudformation:SetStackPolicy", + "cloudformation:ValidateTemplate", + "cloudformation:UpdateStack", + "servicecatalog:DescribeProduct", + "servicecatalog:DescribeProductView", + "servicecatalog:DescribeProvisioningParameters", + "servicecatalog:ListLaunchPaths", + "servicecatalog:ProvisionProduct", + "servicecatalog:SearchProducts", + "s3:GetObject" + ], + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": [ + "servicecatalog:DescribeProvisionedProduct", + "servicecatalog:DescribeRecord", + "servicecatalog:ListRecordHistory", + "servicecatalog:ScanProvisionedProducts", + "servicecatalog:TerminateProvisionedProduct", + "servicecatalog:UpdateProvisionedProduct" + ], + "Condition": { + "StringEquals": { + "servicecatalog:userLevel": "self" + } + }, + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAJIW7AFFOONVKW75KU", + "PolicyName": "ServiceCatalogEndUserFullAccess", + "UpdateDate": "2017-08-08T18:58:54+00:00", + "VersionId": "v4" + }, + "SimpleWorkflowFullAccess": { + "Arn": "arn:aws:iam::aws:policy/SimpleWorkflowFullAccess", + "AttachmentCount": 0, + "CreateDate": "2015-02-06T18:41:04+00:00", + "DefaultVersionId": "v1", + "Document": { + "Statement": [ + { + "Action": [ + "swf:*" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/", + "PolicyId": "ANPAIFE3AV6VE7EANYBVM", + "PolicyName": "SimpleWorkflowFullAccess", + "UpdateDate": "2015-02-06T18:41:04+00:00", + "VersionId": "v1" + }, + "SupportUser": { + "Arn": "arn:aws:iam::aws:policy/job-function/SupportUser", + "AttachmentCount": 0, + "CreateDate": "2017-05-17T23:11:51+00:00", + "DefaultVersionId": "v2", + "Document": { + "Statement": [ + { + "Action": [ + "support:*", + "acm:DescribeCertificate", + "acm:GetCertificate", + "acm:List*", + "apigateway:GET", + "appstream:Get*", + "autoscaling:Describe*", + "aws-marketplace:ViewSubscriptions", + "cloudformation:Describe*", + "cloudformation:Get*", + "cloudformation:List*", + "cloudformation:EstimateTemplateCost", + "cloudfront:Get*", + "cloudfront:List*", + "cloudsearch:Describe*", + "cloudsearch:List*", + "cloudtrail:DescribeTrails", + "cloudtrail:GetTrailStatus", + "cloudtrail:LookupEvents", + "cloudtrail:ListTags", + "cloudtrail:ListPublicKeys", + "cloudwatch:Describe*", + "cloudwatch:Get*", + "cloudwatch:List*", + "codecommit:BatchGetRepositories", + "codecommit:Get*", + "codecommit:List*", + "codedeploy:Batch*", + "codedeploy:Get*", + "codedeploy:List*", + "codepipeline:AcknowledgeJob", + "codepipeline:AcknowledgeThirdPartyJob", + "codepipeline:ListActionTypes", + "codepipeline:ListPipelines", + "codepipeline:PollForJobs", + "codepipeline:PollForThirdPartyJobs", + "codepipeline:GetPipelineState", + "codepipeline:GetPipeline", + "cognito-identity:List*", + "cognito-identity:LookupDeveloperIdentity", + "cognito-identity:Describe*", + "cognito-idp:Describe*", + "cognito-sync:Describe*", + "cognito-sync:GetBulkPublishDetails", + "cognito-sync:GetCognitoEvents", + "cognito-sync:GetIdentityPoolConfiguration", + "cognito-sync:List*", + "config:DescribeConfigurationRecorders", + "config:DescribeConfigurationRecorderStatus", + "config:DescribeConfigRuleEvaluationStatus", + "config:DescribeConfigRules", + "config:DescribeDeliveryChannels", + "config:DescribeDeliveryChannelStatus", + "config:GetResourceConfigHistory", + "config:ListDiscoveredResources", + "datapipeline:DescribeObjects", + "datapipeline:DescribePipelines", + "datapipeline:GetPipelineDefinition", + "datapipeline:ListPipelines", + "datapipeline:QueryObjects", + "datapipeline:ReportTaskProgress", + "datapipeline:ReportTaskRunnerHeartbeat", + "devicefarm:List*", + "devicefarm:Get*", + "directconnect:Describe*", + "discovery:Describe*", + "discovery:ListConfigurations", + "dms:Describe*", + "dms:List*", + "ds:DescribeDirectories", + "ds:DescribeSnapshots", + "ds:GetDirectoryLimits", + "ds:GetSnapshotLimits", + "ds:ListAuthorizedApplications", + "dynamodb:DescribeLimits", + "dynamodb:DescribeTable", + "dynamodb:ListTables", + "ec2:Describe*", + "ec2:DescribeHosts", + "ec2:describeIdentityIdFormat", + "ec2:DescribeIdFormat", + "ec2:DescribeInstanceAttribute", + "ec2:DescribeNatGateways", + "ec2:DescribeReservedInstancesModifications", + "ec2:DescribeTags", + "ec2:GetFlowLogsCount", + "ecr:GetRepositoryPolicy", + "ecr:BatchCheckLayerAvailability", + "ecr:DescribeRepositories", + "ecr:ListImages", + "ecs:Describe*", + "ecs:List*", + "elasticache:Describe*", + "elasticache:List*", + "elasticbeanstalk:Check*", + "elasticbeanstalk:Describe*", + "elasticbeanstalk:List*", + "elasticbeanstalk:RequestEnvironmentInfo", + "elasticbeanstalk:RetrieveEnvironmentInfo", + "elasticbeanstalk:ValidateConfigurationSettings", + "elasticfilesystem:Describe*", + "elasticloadbalancing:Describe*", + "elasticmapreduce:Describe*", + "elasticmapreduce:List*", + "elastictranscoder:List*", + "elastictranscoder:ReadJob", + "elasticfilesystem:DescribeFileSystems", + "es:Describe*", + "es:List*", + "es:ESHttpGet", + "es:ESHttpHead", + "events:DescribeRule", + "events:List*", + "events:TestEventPattern", + "firehose:Describe*", + "firehose:List*", + "gamelift:List*", + "gamelift:Describe*", + "glacier:ListVaults", + "glacier:DescribeVault", + "glacier:DescribeJob", + "glacier:Get*", + "glacier:List*", + "iam:GenerateCredentialReport", + "iam:GenerateServiceLastAccessedDetails", + "iam:Get*", + "iam:List*", + "importexport:GetStatus", + "importexport:ListJobs", + "importexport:GetJobDetail", + "inspector:Describe*", + "inspector:List*", + "inspector:GetAssessmentTelemetry", + "inspector:LocalizeText", + "iot:Describe*", + "iot:Get*", + "iot:List*", + "kinesisanalytics:DescribeApplication", + "kinesisanalytics:DiscoverInputSchema", + "kinesisanalytics:GetApplicationState", + "kinesisanalytics:ListApplications", + "kinesis:Describe*", + "kinesis:Get*", + "kinesis:List*", + "kms:Describe*", + "kms:Get*", + "kms:List*", + "lambda:List*", + "lambda:Get*", + "logs:Describe*", + "logs:TestMetricFilter", + "machinelearning:Describe*", + "machinelearning:Get*", + "mobilehub:GetProject", + "mobilehub:List*", + "mobilehub:ValidateProject", + "mobilehub:VerifyServiceRole", + "opsworks:Describe*", + "rds:Describe*", + "rds:ListTagsForResource", + "redshift:Describe*", + "route53:Get*", + "route53:List*", + "route53domains:CheckDomainAvailability", + "route53domains:GetDomainDetail", + "route53domains:GetOperationDetail", + "route53domains:List*", + "s3:List*", + "sdb:GetAttributes", + "sdb:List*", + "sdb:Select*", + "servicecatalog:SearchProducts", + "servicecatalog:DescribeProduct", + "servicecatalog:DescribeProductView", + "servicecatalog:ListLaunchPaths", + "servicecatalog:DescribeProvisioningParameters", + "servicecatalog:ListRecordHistory", + "servicecatalog:DescribeRecord", + "servicecatalog:ScanProvisionedProducts", + "ses:Get*", + "ses:List*", + "sns:Get*", + "sns:List*", + "sqs:GetQueueAttributes", + "sqs:GetQueueUrl", + "sqs:ListQueues", + "sqs:ReceiveMessage", + "ssm:List*", + "ssm:Describe*", + "storagegateway:Describe*", + "storagegateway:List*", + "swf:Count*", + "swf:Describe*", + "swf:Get*", + "swf:List*", + "waf:Get*", + "waf:List*", + "workspaces:Describe*", + "workdocs:Describe*", + "workmail:Describe*", + "workmail:Get*", + "workspaces:Describe*" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/job-function/", + "PolicyId": "ANPAI3V4GSSN5SJY3P2RO", + "PolicyName": "SupportUser", + "UpdateDate": "2017-05-17T23:11:51+00:00", + "VersionId": "v2" + }, + "SystemAdministrator": { + "Arn": "arn:aws:iam::aws:policy/job-function/SystemAdministrator", + "AttachmentCount": 0, + "CreateDate": "2017-03-24T17:45:43+00:00", + "DefaultVersionId": "v2", + "Document": { + "Statement": [ + { + "Action": [ + "acm:Describe*", + "acm:Get*", + "acm:List*", + "acm:Request*", + "acm:Resend*", + "autoscaling:*", + "cloudtrail:DescribeTrails", + "cloudtrail:GetTrailStatus", + "cloudtrail:ListPublicKeys", + "cloudtrail:ListTags", + "cloudtrail:LookupEvents", + "cloudtrail:StartLogging", + "cloudtrail:StopLogging", + "cloudwatch:*", + "codecommit:BatchGetRepositories", + "codecommit:CreateBranch", + "codecommit:CreateRepository", + "codecommit:Get*", + "codecommit:GitPull", + "codecommit:GitPush", + "codecommit:List*", + "codecommit:Put*", + "codecommit:Test*", + "codecommit:Update*", + "codedeploy:*", + "codepipeline:*", + "config:*", + "ds:*", + "ec2:Allocate*", + "ec2:AssignPrivateIpAddresses*", + "ec2:Associate*", + "ec2:Allocate*", + "ec2:AttachInternetGateway", + "ec2:AttachNetworkInterface", + "ec2:AttachVpnGateway", + "ec2:Bundle*", + "ec2:Cancel*", + "ec2:Copy*", + "ec2:CreateCustomerGateway", + "ec2:CreateDhcpOptions", + "ec2:CreateFlowLogs", + "ec2:CreateImage", + "ec2:CreateInstanceExportTask", + "ec2:CreateInternetGateway", + "ec2:CreateKeyPair", + "ec2:CreateNatGateway", + "ec2:CreateNetworkInterface", + "ec2:CreatePlacementGroup", + "ec2:CreateReservedInstancesListing", + "ec2:CreateRoute", + "ec2:CreateRouteTable", + "ec2:CreateSecurityGroup", + "ec2:CreateSnapshot", + "ec2:CreateSpotDatafeedSubscription", + "ec2:CreateSubnet", + "ec2:CreateTags", + "ec2:CreateVolume", + "ec2:CreateVpc", + "ec2:CreateVpcEndpoint", + "ec2:CreateVpnConnection", + "ec2:CreateVpnConnectionRoute", + "ec2:CreateVpnGateway", + "ec2:DeleteFlowLogs", + "ec2:DeleteKeyPair", + "ec2:DeleteNatGateway", + "ec2:DeleteNetworkInterface", + "ec2:DeletePlacementGroup", + "ec2:DeleteSnapshot", + "ec2:DeleteSpotDatafeedSubscription", + "ec2:DeleteSubnet", + "ec2:DeleteTags", + "ec2:DeleteVpc", + "ec2:DeleteVpcEndpoints", + "ec2:DeleteVpnConnection", + "ec2:DeleteVpnConnectionRoute", + "ec2:DeleteVpnGateway", + "ec2:DeregisterImage", + "ec2:Describe*", + "ec2:DetachInternetGateway", + "ec2:DetachNetworkInterface", + "ec2:DetachVpnGateway", + "ec2:DisableVgwRoutePropagation", + "ec2:DisableVpcClassicLinkDnsSupport", + "ec2:DisassociateAddress", + "ec2:DisassociateRouteTable", + "ec2:EnableVgwRoutePropagation", + "ec2:EnableVolumeIO", + "ec2:EnableVpcClassicLinkDnsSupport", + "ec2:GetConsoleOutput", + "ec2:GetHostReservationPurchasePreview", + "ec2:GetPasswordData", + "ec2:Import*", + "ec2:Modify*", + "ec2:MonitorInstances", + "ec2:MoveAddressToVpc", + "ec2:Purchase*", + "ec2:RegisterImage", + "ec2:Release*", + "ec2:Replace*", + "ec2:ReportInstanceStatus", + "ec2:Request*", + "ec2:Reset*", + "ec2:RestoreAddressToClassic", + "ec2:RunScheduledInstances", + "ec2:UnassignPrivateIpAddresses", + "ec2:UnmonitorInstances", + "elasticloadbalancing:*", + "events:*", + "iam:GetAccount*", + "iam:GetContextKeys*", + "iam:GetCredentialReport", + "iam:ListAccountAliases", + "iam:ListGroups", + "iam:ListOpenIDConnectProviders", + "iam:ListPolicies", + "iam:ListPoliciesGrantingServiceAccess", + "iam:ListRoles", + "iam:ListSAMLProviders", + "iam:ListServerCertificates", + "iam:Simulate*", + "iam:UpdateServerCertificate", + "iam:UpdateSigningCertificate", + "kinesis:ListStreams", + "kinesis:PutRecord", + "kms:CreateAlias", + "kms:CreateKey", + "kms:DeleteAlias", + "kms:Describe*", + "kms:GenerateRandom", + "kms:Get*", + "kms:List*", + "kms:Encrypt", + "kms:ReEncrypt*", + "lambda:Create*", + "lambda:Delete*", + "lambda:Get*", + "lambda:InvokeFunction", + "lambda:List*", + "lambda:PublishVersion", + "lambda:Update*", + "logs:*", + "rds:Describe*", + "rds:ListTagsForResource", + "route53:*", + "route53domains:*", + "ses:*", + "sns:*", + "sqs:*", + "trustedadvisor:*" + ], + "Effect": "Allow", + "Resource": "*" + }, + { + "Action": [ + "ec2:AcceptVpcPeeringConnection", + "ec2:AttachClassicLinkVpc", + "ec2:AttachVolume", + "ec2:AuthorizeSecurityGroupEgress", + "ec2:AuthorizeSecurityGroupIngress", + "ec2:CreateVpcPeeringConnection", + "ec2:DeleteCustomerGateway", + "ec2:DeleteDhcpOptions", + "ec2:DeleteInternetGateway", + "ec2:DeleteNetworkAcl*", + "ec2:DeleteRoute", + "ec2:DeleteRouteTable", + "ec2:DeleteSecurityGroup", + "ec2:DeleteVolume", + "ec2:DeleteVpcPeeringConnection", + "ec2:DetachClassicLinkVpc", + "ec2:DetachVolume", + "ec2:DisableVpcClassicLink", + "ec2:EnableVpcClassicLink", + "ec2:GetConsoleScreenshot", + "ec2:RebootInstances", + "ec2:RejectVpcPeeringConnection", + "ec2:RevokeSecurityGroupEgress", + "ec2:RevokeSecurityGroupIngress", + "ec2:RunInstances", + "ec2:StartInstances", + "ec2:StopInstances", + "ec2:TerminateInstances" + ], + "Effect": "Allow", + "Resource": [ + "*" + ] + }, + { + "Action": "s3:*", + "Effect": "Allow", + "Resource": [ + "*" + ] + }, + { + "Action": [ + "iam:GetAccessKeyLastUsed", + "iam:GetGroup*", + "iam:GetInstanceProfile", + "iam:GetLoginProfile", + "iam:GetOpenIDConnectProvider", + "iam:GetPolicy*", + "iam:GetRole*", + "iam:GetSAMLProvider", + "iam:GetSSHPublicKey", + "iam:GetServerCertificate", + "iam:GetServiceLastAccessed*", + "iam:GetUser*", + "iam:ListAccessKeys", + "iam:ListAttached*", + "iam:ListEntitiesForPolicy", + "iam:ListGroupPolicies", + "iam:ListGroupsForUser", + "iam:ListInstanceProfiles*", + "iam:ListMFADevices", + "iam:ListPolicyVersions", + "iam:ListRolePolicies", + "iam:ListSSHPublicKeys", + "iam:ListSigningCertificates", + "iam:ListUserPolicies", + "iam:Upload*" + ], + "Effect": "Allow", + "Resource": [ + "*" + ] + }, + { + "Action": [ + "iam:GetRole", + "iam:ListRoles", + "iam:PassRole" + ], + "Effect": "Allow", + "Resource": [ + "arn:aws:iam::*:role/rds-monitoring-role", + "arn:aws:iam::*:role/ec2-sysadmin-*", + "arn:aws:iam::*:role/ecr-sysadmin-*", + "arn:aws:iam::*:role/lamdba-sysadmin-*" + ] + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/job-function/", + "PolicyId": "ANPAITJPEZXCYCBXANDSW", + "PolicyName": "SystemAdministrator", + "UpdateDate": "2017-03-24T17:45:43+00:00", + "VersionId": "v2" + }, + "VMImportExportRoleForAWSConnector": { + "Arn": "arn:aws:iam::aws:policy/service-role/VMImportExportRoleForAWSConnector", + "AttachmentCount": 0, + "CreateDate": "2015-09-03T20:48:59+00:00", + "DefaultVersionId": "v1", + "Document": { + "Statement": [ + { + "Action": [ + "s3:ListBucket", + "s3:GetBucketLocation", + "s3:GetObject" + ], + "Effect": "Allow", + "Resource": [ + "arn:aws:s3:::import-to-ec2-*" + ] + }, + { + "Action": [ + "ec2:ModifySnapshotAttribute", + "ec2:CopySnapshot", + "ec2:RegisterImage", + "ec2:Describe*" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/service-role/", + "PolicyId": "ANPAJFLQOOJ6F5XNX4LAW", + "PolicyName": "VMImportExportRoleForAWSConnector", + "UpdateDate": "2015-09-03T20:48:59+00:00", + "VersionId": "v1" + }, + "ViewOnlyAccess": { + "Arn": "arn:aws:iam::aws:policy/job-function/ViewOnlyAccess", + "AttachmentCount": 0, + "CreateDate": "2017-06-26T22:35:31+00:00", + "DefaultVersionId": "v3", + "Document": { + "Statement": [ + { + "Action": [ + "acm:ListCertificates", + "athena:List*", + "aws-marketplace:ViewSubscriptions", + "autoscaling:Describe*", + "batch:ListJobs", + "clouddirectory:ListAppliedSchemaArns", + "clouddirectory:ListDevelopmentSchemaArns", + "clouddirectory:ListDirectories", + "clouddirectory:ListPublishedSchemaArns", + "cloudformation:List*", + "cloudformation:DescribeStacks", + "cloudfront:List*", + "cloudhsm:ListAvailableZones", + "cloudhsm:ListLunaClients", + "cloudhsm:ListHapgs", + "cloudhsm:ListHsms", + "cloudsearch:List*", + "cloudsearch:DescribeDomains", + "cloudtrail:DescribeTrails", + "cloudtrail:LookupEvents", + "cloudwatch:List*", + "cloudwatch:GetMetricData", + "codebuild:ListBuilds*", + "codebuild:ListProjects", + "codecommit:List*", + "codedeploy:List*", + "codedeploy:Get*", + "codepipeline:ListPipelines", + "codestar:List*", + "codestar:Verify*", + "cognito-idp:List*", + "cognito-identity:ListIdentities", + "cognito-identity:ListIdentityPools", + "cognito-sync:ListDatasets", + "connect:List*", + "config:List*", + "config:Describe*", + "datapipeline:ListPipelines", + "datapipeline:DescribePipelines", + "datapipeline:GetAccountLimits", + "devicefarm:List*", + "directconnect:Describe*", + "discovery:List*", + "dms:List*", + "ds:DescribeDirectories", + "dynamodb:ListTables", + "ec2:DescribeAccountAttributes", + "ec2:DescribeAddresses", + "ec2:DescribeAvailabilityZones", + "ec2:DescribeBundleTasks", + "ec2:DescribeClassicLinkInstances", + "ec2:DescribeConversionTasks", + "ec2:DescribeCustomerGateways", + "ec2:DescribeDhcpOptions", + "ec2:DescribeExportTasks", + "ec2:DescribeFlowLogs", + "ec2:DescribeHost*", + "ec2:DescribeIdentityIdFormat", + "ec2:DescribeIdFormat", + "ec2:DescribeImage*", + "ec2:DescribeImport*", + "ec2:DescribeInstance*", + "ec2:DescribeInternetGateways", + "ec2:DescribeKeyPairs", + "ec2:DescribeMovingAddresses", + "ec2:DescribeNatGateways", + "ec2:DescribeNetwork*", + "ec2:DescribePlacementGroups", + "ec2:DescribePrefixLists", + "ec2:DescribeRegions", + "ec2:DescribeReserved*", + "ec2:DescribeRouteTables", + "ec2:DescribeSecurityGroups", + "ec2:DescribeSnapshot*", + "ec2:DescribeSpot*", + "ec2:DescribeSubnets", + "ec2:DescribeVolume*", + "ec2:DescribeVpc*", + "ec2:DescribeVpnGateways", + "ecr:DescribeRepositories", + "ecr:ListImages", + "ecs:List*", + "elasticache:Describe*", + "elasticbeanstalk:DescribeApplicationVersions", + "elasticbeanstalk:DescribeApplications", + "elasticbeanstalk:DescribeEnvironments", + "elasticbeanstalk:ListAvailableSolutionStacks", + "elasticloadbalancing:DescribeListeners", + "elasticloadbalancing:DescribeLoadBalancers", + "elasticloadbalancing:DescribeTargetGroups", + "elasticloadbalancing:DescribeTargetHealth", + "elasticfilesystem:DescribeFileSystems", + "elasticmapreduce:List*", + "elastictranscoder:List*", + "es:DescribeElasticsearchDomain", + "es:DescribeElasticsearchDomains", + "es:ListDomainNames", + "events:ListRuleNamesByTarget", + "events:ListRules", + "events:ListTargetsByRule", + "firehose:List*", + "firehose:DescribeDeliveryStream", + "gamelift:List*", + "glacier:List*", + "iam:List*", + "iam:GetAccountSummary", + "iam:GetLoginProfile", + "importexport:ListJobs", + "inspector:List*", + "iot:List*", + "kinesis:ListStreams", + "kinesisanalytics:ListApplications", + "kms:ListKeys", + "lambda:List*", + "lex:GetBotAliases", + "lex:GetBotChannelAssociations", + "lex:GetBots", + "lex:GetBotVersions", + "lex:GetIntents", + "lex:GetIntentVersions", + "lex:GetSlotTypes", + "lex:GetSlotTypeVersions", + "lex:GetUtterancesView", + "lightsail:GetBlueprints", + "lightsail:GetBundles", + "lightsail:GetInstances", + "lightsail:GetInstanceSnapshots", + "lightsail:GetKeyPair", + "lightsail:GetRegions", + "lightsail:GetStaticIps", + "lightsail:IsVpcPeered", + "logs:Describe*", + "machinelearning:Describe*", + "mobilehub:ListAvailableFeatures", + "mobilehub:ListAvailableRegions", + "mobilehub:ListProjects", + "opsworks:Describe*", + "opsworks-cm:Describe*", + "organizations:List*", + "mobiletargeting:GetApplicationSettings", + "mobiletargeting:GetCampaigns", + "mobiletargeting:GetImportJobs", + "mobiletargeting:GetSegments", + "polly:Describe*", + "polly:List*", + "rds:Describe*", + "redshift:DescribeClusters", + "redshift:DescribeEvents", + "redshift:ViewQueriesInConsole", + "route53:List*", + "route53:Get*", + "route53domains:List*", + "s3:ListAllMyBuckets", + "s3:ListBucket", + "sdb:List*", + "servicecatalog:List*", + "ses:List*", + "shield:List*", + "states:ListActivities", + "states:ListStateMachines", + "sns:List*", + "sqs:ListQueues", + "ssm:ListAssociations", + "ssm:ListDocuments", + "storagegateway:ListGateways", + "storagegateway:ListLocalDisks", + "storagegateway:ListVolumeRecoveryPoints", + "storagegateway:ListVolumes", + "swf:List*", + "trustedadvisor:Describe*", + "waf:List*", + "waf-regional:List*", + "workdocs:DescribeAvailableDirectories", + "workdocs:DescribeInstances", + "workmail:Describe*", + "workspaces:Describe*" + ], + "Effect": "Allow", + "Resource": "*" + } + ], + "Version": "2012-10-17" + }, + "IsAttachable": true, + "IsDefaultVersion": true, + "Path": "/job-function/", + "PolicyId": "ANPAID22R6XPJATWOFDK6", + "PolicyName": "ViewOnlyAccess", + "UpdateDate": "2017-06-26T22:35:31+00:00", + "VersionId": "v3" + } +}""" diff --git a/moto/iam/models.py b/moto/iam/models.py index 1e4b58578..18ed513b4 100644 --- a/moto/iam/models.py +++ b/moto/iam/models.py @@ -1,11 +1,13 @@ from __future__ import unicode_literals import base64 from datetime import datetime +import json import pytz from moto.core import BaseBackend, BaseModel from moto.core.utils import iso_8601_datetime_without_milliseconds +from .aws_managed_policies import aws_managed_policies_data from .exceptions import IAMNotFoundException, IAMConflictException, IAMReportNotPresentException from .utils import random_access_key, random_alphanumeric, random_resource_id, random_policy_id @@ -72,14 +74,32 @@ class ManagedPolicy(Policy): is_attachable = True - def attach_to_role(self, role): + def attach_to(self, obj): self.attachment_count += 1 - role.managed_policies[self.name] = self + obj.managed_policies[self.name] = self + + def detach_from(self, obj): + self.attachment_count -= 1 + del obj.managed_policies[self.name] class AWSManagedPolicy(ManagedPolicy): """AWS-managed policy.""" + @classmethod + def from_data(cls, name, data): + return cls(name, + default_version_id=data.get('DefaultVersionId'), + path=data.get('Path'), + document=data.get('Document')) + + +# AWS defines some of its own managed policies and we periodically +# import them via `make aws_managed_policies` +aws_managed_policies = [ + AWSManagedPolicy.from_data(name, d) for name, d + in json.loads(aws_managed_policies_data).items()] + class InlinePolicy(Policy): """TODO: is this needed?""" @@ -120,6 +140,13 @@ class Role(BaseModel): def put_policy(self, policy_name, policy_json): self.policies[policy_name] = policy_json + def delete_policy(self, policy_name): + try: + del self.policies[policy_name] + except KeyError: + raise IAMNotFoundException( + "The role policy with name {0} cannot be found.".format(policy_name)) + @property def physical_resource_id(self): return self.id @@ -214,6 +241,7 @@ class Group(BaseModel): ) self.users = [] + self.managed_policies = {} self.policies = {} def get_cfn_attribute(self, attribute_name): @@ -254,6 +282,7 @@ class User(BaseModel): self.created = datetime.utcnow() self.mfa_devices = {} self.policies = {} + self.managed_policies = {} self.access_keys = [] self.password = None self.password_reset_required = False @@ -368,115 +397,6 @@ class User(BaseModel): ) -# predefine AWS managed policies -aws_managed_policies = [ - AWSManagedPolicy( - 'AmazonElasticMapReduceRole', - default_version_id='v6', - path='/service-role/', - document={ - "Version": "2012-10-17", - "Statement": [{ - "Effect": "Allow", - "Resource": "*", - "Action": [ - "ec2:AuthorizeSecurityGroupEgress", - "ec2:AuthorizeSecurityGroupIngress", - "ec2:CancelSpotInstanceRequests", - "ec2:CreateNetworkInterface", - "ec2:CreateSecurityGroup", - "ec2:CreateTags", - "ec2:DeleteNetworkInterface", - "ec2:DeleteSecurityGroup", - "ec2:DeleteTags", - "ec2:DescribeAvailabilityZones", - "ec2:DescribeAccountAttributes", - "ec2:DescribeDhcpOptions", - "ec2:DescribeInstanceStatus", - "ec2:DescribeInstances", - "ec2:DescribeKeyPairs", - "ec2:DescribeNetworkAcls", - "ec2:DescribeNetworkInterfaces", - "ec2:DescribePrefixLists", - "ec2:DescribeRouteTables", - "ec2:DescribeSecurityGroups", - "ec2:DescribeSpotInstanceRequests", - "ec2:DescribeSpotPriceHistory", - "ec2:DescribeSubnets", - "ec2:DescribeVpcAttribute", - "ec2:DescribeVpcEndpoints", - "ec2:DescribeVpcEndpointServices", - "ec2:DescribeVpcs", - "ec2:DetachNetworkInterface", - "ec2:ModifyImageAttribute", - "ec2:ModifyInstanceAttribute", - "ec2:RequestSpotInstances", - "ec2:RevokeSecurityGroupEgress", - "ec2:RunInstances", - "ec2:TerminateInstances", - "ec2:DeleteVolume", - "ec2:DescribeVolumeStatus", - "ec2:DescribeVolumes", - "ec2:DetachVolume", - "iam:GetRole", - "iam:GetRolePolicy", - "iam:ListInstanceProfiles", - "iam:ListRolePolicies", - "iam:PassRole", - "s3:CreateBucket", - "s3:Get*", - "s3:List*", - "sdb:BatchPutAttributes", - "sdb:Select", - "sqs:CreateQueue", - "sqs:Delete*", - "sqs:GetQueue*", - "sqs:PurgeQueue", - "sqs:ReceiveMessage" - ] - }] - } - ), - AWSManagedPolicy( - 'AmazonElasticMapReduceforEC2Role', - default_version_id='v2', - path='/service-role/', - document={ - "Version": "2012-10-17", - "Statement": [{ - "Effect": "Allow", - "Resource": "*", - "Action": [ - "cloudwatch:*", - "dynamodb:*", - "ec2:Describe*", - "elasticmapreduce:Describe*", - "elasticmapreduce:ListBootstrapActions", - "elasticmapreduce:ListClusters", - "elasticmapreduce:ListInstanceGroups", - "elasticmapreduce:ListInstances", - "elasticmapreduce:ListSteps", - "kinesis:CreateStream", - "kinesis:DeleteStream", - "kinesis:DescribeStream", - "kinesis:GetRecords", - "kinesis:GetShardIterator", - "kinesis:MergeShards", - "kinesis:PutRecord", - "kinesis:SplitShard", - "rds:Describe*", - "s3:*", - "sdb:*", - "sns:*", - "sqs:*" - ] - }] - } - ) -] -# TODO: add more predefined AWS managed policies - - class IAMBackend(BaseBackend): def __init__(self): @@ -487,6 +407,7 @@ class IAMBackend(BaseBackend): self.users = {} self.credential_report = None self.managed_policies = self._init_managed_policies() + self.account_aliases = [] super(IAMBackend, self).__init__() def _init_managed_policies(self): @@ -495,7 +416,47 @@ class IAMBackend(BaseBackend): def attach_role_policy(self, policy_arn, role_name): arns = dict((p.arn, p) for p in self.managed_policies.values()) policy = arns[policy_arn] - policy.attach_to_role(self.get_role(role_name)) + policy.attach_to(self.get_role(role_name)) + + def detach_role_policy(self, policy_arn, role_name): + arns = dict((p.arn, p) for p in self.managed_policies.values()) + try: + policy = arns[policy_arn] + policy.detach_from(self.get_role(role_name)) + except KeyError: + raise IAMNotFoundException("Policy {0} was not found.".format(policy_arn)) + + def attach_group_policy(self, policy_arn, group_name): + arns = dict((p.arn, p) for p in self.managed_policies.values()) + try: + policy = arns[policy_arn] + except KeyError: + raise IAMNotFoundException("Policy {0} was not found.".format(policy_arn)) + policy.attach_to(self.get_group(group_name)) + + def detach_group_policy(self, policy_arn, group_name): + arns = dict((p.arn, p) for p in self.managed_policies.values()) + try: + policy = arns[policy_arn] + except KeyError: + raise IAMNotFoundException("Policy {0} was not found.".format(policy_arn)) + policy.detach_from(self.get_group(group_name)) + + def attach_user_policy(self, policy_arn, user_name): + arns = dict((p.arn, p) for p in self.managed_policies.values()) + try: + policy = arns[policy_arn] + except KeyError: + raise IAMNotFoundException("Policy {0} was not found.".format(policy_arn)) + policy.attach_to(self.get_user(user_name)) + + def detach_user_policy(self, policy_arn, user_name): + arns = dict((p.arn, p) for p in self.managed_policies.values()) + try: + policy = arns[policy_arn] + except KeyError: + raise IAMNotFoundException("Policy {0} was not found.".format(policy_arn)) + policy.detach_from(self.get_user(user_name)) def create_policy(self, description, path, policy_document, policy_name): policy = ManagedPolicy( @@ -512,21 +473,15 @@ class IAMBackend(BaseBackend): def list_attached_role_policies(self, role_name, marker=None, max_items=100, path_prefix='/'): policies = self.get_role(role_name).managed_policies.values() + return self._filter_attached_policies(policies, marker, max_items, path_prefix) - if path_prefix: - policies = [p for p in policies if p.path.startswith(path_prefix)] + def list_attached_group_policies(self, group_name, marker=None, max_items=100, path_prefix='/'): + policies = self.get_group(group_name).managed_policies.values() + return self._filter_attached_policies(policies, marker, max_items, path_prefix) - policies = sorted(policies, key=lambda policy: policy.name) - start_idx = int(marker) if marker else 0 - - policies = policies[start_idx:start_idx + max_items] - - if len(policies) < max_items: - marker = None - else: - marker = str(start_idx + max_items) - - return policies, marker + def list_attached_user_policies(self, user_name, marker=None, max_items=100, path_prefix='/'): + policies = self.get_user(user_name).managed_policies.values() + return self._filter_attached_policies(policies, marker, max_items, path_prefix) def list_policies(self, marker, max_items, only_attached, path_prefix, scope): policies = self.managed_policies.values() @@ -540,6 +495,9 @@ class IAMBackend(BaseBackend): policies = [p for p in policies if not isinstance( p, AWSManagedPolicy)] + return self._filter_attached_policies(policies, marker, max_items, path_prefix) + + def _filter_attached_policies(self, policies, marker, max_items, path_prefix): if path_prefix: policies = [p for p in policies if p.path.startswith(path_prefix)] @@ -584,6 +542,10 @@ class IAMBackend(BaseBackend): role = self.get_role(role_name) role.put_policy(policy_name, policy_json) + def delete_role_policy(self, role_name, policy_name): + role = self.get_role(role_name) + role.delete_policy(policy_name) + def get_role_policy(self, role_name, policy_name): role = self.get_role(role_name) for p, d in role.policies.items(): @@ -897,5 +859,15 @@ class IAMBackend(BaseBackend): report += self.users[user].to_csv() return base64.b64encode(report.encode('ascii')).decode('ascii') + def list_account_aliases(self): + return self.account_aliases + + def create_account_alias(self, alias): + # alias is force updated + self.account_aliases = [alias] + + def delete_account_alias(self, alias): + self.account_aliases = [] + iam_backend = IAMBackend() diff --git a/moto/iam/responses.py b/moto/iam/responses.py index a5e5081c3..6ca49b830 100644 --- a/moto/iam/responses.py +++ b/moto/iam/responses.py @@ -13,6 +13,41 @@ class IamResponse(BaseResponse): template = self.response_template(ATTACH_ROLE_POLICY_TEMPLATE) return template.render() + def detach_role_policy(self): + role_name = self._get_param('RoleName') + policy_arn = self._get_param('PolicyArn') + iam_backend.detach_role_policy(policy_arn, role_name) + template = self.response_template(GENERIC_EMPTY_TEMPLATE) + return template.render(name="DetachRolePolicyResponse") + + def attach_group_policy(self): + policy_arn = self._get_param('PolicyArn') + group_name = self._get_param('GroupName') + iam_backend.attach_group_policy(policy_arn, group_name) + template = self.response_template(ATTACH_GROUP_POLICY_TEMPLATE) + return template.render() + + def detach_group_policy(self): + policy_arn = self._get_param('PolicyArn') + group_name = self._get_param('GroupName') + iam_backend.detach_group_policy(policy_arn, group_name) + template = self.response_template(DETACH_GROUP_POLICY_TEMPLATE) + return template.render() + + def attach_user_policy(self): + policy_arn = self._get_param('PolicyArn') + user_name = self._get_param('UserName') + iam_backend.attach_user_policy(policy_arn, user_name) + template = self.response_template(ATTACH_USER_POLICY_TEMPLATE) + return template.render() + + def detach_user_policy(self): + policy_arn = self._get_param('PolicyArn') + user_name = self._get_param('UserName') + iam_backend.detach_user_policy(policy_arn, user_name) + template = self.response_template(DETACH_USER_POLICY_TEMPLATE) + return template.render() + def create_policy(self): description = self._get_param('Description') path = self._get_param('Path') @@ -33,6 +68,28 @@ class IamResponse(BaseResponse): template = self.response_template(LIST_ATTACHED_ROLE_POLICIES_TEMPLATE) return template.render(policies=policies, marker=marker) + def list_attached_group_policies(self): + marker = self._get_param('Marker') + max_items = self._get_int_param('MaxItems', 100) + path_prefix = self._get_param('PathPrefix', '/') + group_name = self._get_param('GroupName') + policies, marker = iam_backend.list_attached_group_policies( + group_name, marker=marker, max_items=max_items, + path_prefix=path_prefix) + template = self.response_template(LIST_ATTACHED_GROUP_POLICIES_TEMPLATE) + return template.render(policies=policies, marker=marker) + + def list_attached_user_policies(self): + marker = self._get_param('Marker') + max_items = self._get_int_param('MaxItems', 100) + path_prefix = self._get_param('PathPrefix', '/') + user_name = self._get_param('UserName') + policies, marker = iam_backend.list_attached_user_policies( + user_name, marker=marker, max_items=max_items, + path_prefix=path_prefix) + template = self.response_template(LIST_ATTACHED_USER_POLICIES_TEMPLATE) + return template.render(policies=policies, marker=marker) + def list_policies(self): marker = self._get_param('Marker') max_items = self._get_int_param('MaxItems', 100) @@ -82,6 +139,13 @@ class IamResponse(BaseResponse): template = self.response_template(GENERIC_EMPTY_TEMPLATE) return template.render(name="PutRolePolicyResponse") + def delete_role_policy(self): + role_name = self._get_param('RoleName') + policy_name = self._get_param('PolicyName') + iam_backend.delete_role_policy(role_name, policy_name) + template = self.response_template(GENERIC_EMPTY_TEMPLATE) + return template.render(name="DeleteRolePolicyResponse") + def get_role_policy(self): role_name = self._get_param('RoleName') policy_name = self._get_param('PolicyName') @@ -439,6 +503,23 @@ class IamResponse(BaseResponse): template = self.response_template(CREDENTIAL_REPORT) return template.render(report=report) + def list_account_aliases(self): + aliases = iam_backend.list_account_aliases() + template = self.response_template(LIST_ACCOUNT_ALIASES_TEMPLATE) + return template.render(aliases=aliases) + + def create_account_alias(self): + alias = self._get_param('AccountAlias') + iam_backend.create_account_alias(alias) + template = self.response_template(CREATE_ACCOUNT_ALIAS_TEMPLATE) + return template.render() + + def delete_account_alias(self): + alias = self._get_param('AccountAlias') + iam_backend.delete_account_alias(alias) + template = self.response_template(DELETE_ACCOUNT_ALIAS_TEMPLATE) + return template.render() + ATTACH_ROLE_POLICY_TEMPLATE = """ @@ -446,6 +527,36 @@ ATTACH_ROLE_POLICY_TEMPLATE = """ """ +DETACH_ROLE_POLICY_TEMPLATE = """ + + 7a62c49f-347e-4fc4-9331-6e8eEXAMPLE + +""" + +ATTACH_USER_POLICY_TEMPLATE = """ + + 7a62c49f-347e-4fc4-9331-6e8eEXAMPLE + +""" + +DETACH_USER_POLICY_TEMPLATE = """ + + 7a62c49f-347e-4fc4-9331-6e8eEXAMPLE + +""" + +ATTACH_GROUP_POLICY_TEMPLATE = """ + + 7a62c49f-347e-4fc4-9331-6e8eEXAMPLE + +""" + +DETACH_GROUP_POLICY_TEMPLATE = """ + + 7a62c49f-347e-4fc4-9331-6e8eEXAMPLE + +""" + CREATE_POLICY_TEMPLATE = """ @@ -486,6 +597,50 @@ LIST_ATTACHED_ROLE_POLICIES_TEMPLATE = """ """ +LIST_ATTACHED_GROUP_POLICIES_TEMPLATE = """ + + {% if marker is none %} + false + {% else %} + true + {{ marker }} + {% endif %} + + {% for policy in policies %} + + {{ policy.name }} + {{ policy.arn }} + + {% endfor %} + + + + 7a62c49f-347e-4fc4-9331-6e8eEXAMPLE + +""" + +LIST_ATTACHED_USER_POLICIES_TEMPLATE = """ + + {% if marker is none %} + false + {% else %} + true + {{ marker }} + {% endif %} + + {% for policy in policies %} + + {{ policy.name }} + {{ policy.arn }} + + {% endfor %} + + + + 7a62c49f-347e-4fc4-9331-6e8eEXAMPLE + +""" + LIST_POLICIES_TEMPLATE = """ {% if marker is none %} @@ -1113,3 +1268,32 @@ LIST_MFA_DEVICES_TEMPLATE = """ 7a62c49f-347e-4fc4-9331-6e8eEXAMPLE """ + + +LIST_ACCOUNT_ALIASES_TEMPLATE = """ + + false + + {% for alias in aliases %} + {{ alias }} + {% endfor %} + + + + c5a076e9-f1b0-11df-8fbe-45274EXAMPLE + +""" + + +CREATE_ACCOUNT_ALIAS_TEMPLATE = """ + + 36b5db08-f1b0-11df-8fbe-45274EXAMPLE + +""" + + +DELETE_ACCOUNT_ALIAS_TEMPLATE = """ + + 7a62c49f-347e-4fc4-9331-6e8eEXAMPLE + +""" diff --git a/moto/logs/__init__.py b/moto/logs/__init__.py new file mode 100644 index 000000000..f325243fc --- /dev/null +++ b/moto/logs/__init__.py @@ -0,0 +1,5 @@ +from .models import logs_backends +from ..core.models import base_decorator, deprecated_base_decorator + +mock_logs = base_decorator(logs_backends) +mock_logs_deprecated = deprecated_base_decorator(logs_backends) diff --git a/moto/logs/models.py b/moto/logs/models.py new file mode 100644 index 000000000..14f511932 --- /dev/null +++ b/moto/logs/models.py @@ -0,0 +1,228 @@ +from moto.core import BaseBackend +import boto.logs +from moto.core.utils import unix_time_millis + + +class LogEvent: + _event_id = 0 + + def __init__(self, ingestion_time, log_event): + self.ingestionTime = ingestion_time + self.timestamp = log_event["timestamp"] + self.message = log_event['message'] + self.eventId = self.__class__._event_id + self.__class__._event_id += 1 + + def to_filter_dict(self): + return { + "eventId": self.eventId, + "ingestionTime": self.ingestionTime, + # "logStreamName": + "message": self.message, + "timestamp": self.timestamp + } + + +class LogStream: + _log_ids = 0 + + def __init__(self, region, log_group, name): + self.region = region + self.arn = "arn:aws:logs:{region}:{id}:log-group:{log_group}:log-stream:{log_stream}".format( + region=region, id=self.__class__._log_ids, log_group=log_group, log_stream=name) + self.creationTime = unix_time_millis() + self.firstEventTimestamp = None + self.lastEventTimestamp = None + self.lastIngestionTime = None + self.logStreamName = name + self.storedBytes = 0 + self.uploadSequenceToken = 0 # I'm guessing this is token needed for sequenceToken by put_events + self.events = [] + + self.__class__._log_ids += 1 + + def to_describe_dict(self): + return { + "arn": self.arn, + "creationTime": self.creationTime, + "firstEventTimestamp": self.firstEventTimestamp, + "lastEventTimestamp": self.lastEventTimestamp, + "lastIngestionTime": self.lastIngestionTime, + "logStreamName": self.logStreamName, + "storedBytes": self.storedBytes, + "uploadSequenceToken": str(self.uploadSequenceToken), + } + + def put_log_events(self, log_group_name, log_stream_name, log_events, sequence_token): + # TODO: ensure sequence_token + # TODO: to be thread safe this would need a lock + self.lastIngestionTime = unix_time_millis() + # TODO: make this match AWS if possible + self.storedBytes += sum([len(log_event["message"]) for log_event in log_events]) + self.events += [LogEvent(self.lastIngestionTime, log_event) for log_event in log_events] + self.uploadSequenceToken += 1 + + return self.uploadSequenceToken + + def get_log_events(self, log_group_name, log_stream_name, start_time, end_time, limit, next_token, start_from_head): + def filter_func(event): + if start_time and event.timestamp < start_time: + return False + + if end_time and event.timestamp > end_time: + return False + + return True + + events = sorted(filter(filter_func, self.events), key=lambda event: event.timestamp, reverse=start_from_head) + back_token = next_token + if next_token is None: + next_token = 0 + + events_page = events[next_token: next_token + limit] + next_token += limit + if next_token >= len(self.events): + next_token = None + + return events_page, back_token, next_token + + def filter_log_events(self, log_group_name, log_stream_names, start_time, end_time, limit, next_token, filter_pattern, interleaved): + def filter_func(event): + if start_time and event.timestamp < start_time: + return False + + if end_time and event.timestamp > end_time: + return False + + return True + + events = [] + for event in sorted(filter(filter_func, self.events), key=lambda x: x.timestamp): + event_obj = event.to_filter_dict() + event_obj['logStreamName'] = self.logStreamName + events.append(event_obj) + return events + + +class LogGroup: + def __init__(self, region, name, tags): + self.name = name + self.region = region + self.tags = tags + self.streams = dict() # {name: LogStream} + + def create_log_stream(self, log_stream_name): + assert log_stream_name not in self.streams + self.streams[log_stream_name] = LogStream(self.region, self.name, log_stream_name) + + def delete_log_stream(self, log_stream_name): + assert log_stream_name in self.streams + del self.streams[log_stream_name] + + def describe_log_streams(self, descending, limit, log_group_name, log_stream_name_prefix, next_token, order_by): + log_streams = [stream.to_describe_dict() for name, stream in self.streams.items() if name.startswith(log_stream_name_prefix)] + + def sorter(stream): + return stream.name if order_by == 'logStreamName' else stream.lastEventTimestamp + + if next_token is None: + next_token = 0 + + log_streams = sorted(log_streams, key=sorter, reverse=descending) + new_token = next_token + limit + log_streams_page = log_streams[next_token: new_token] + if new_token >= len(log_streams): + new_token = None + + return log_streams_page, new_token + + def put_log_events(self, log_group_name, log_stream_name, log_events, sequence_token): + assert log_stream_name in self.streams + stream = self.streams[log_stream_name] + return stream.put_log_events(log_group_name, log_stream_name, log_events, sequence_token) + + def get_log_events(self, log_group_name, log_stream_name, start_time, end_time, limit, next_token, start_from_head): + assert log_stream_name in self.streams + stream = self.streams[log_stream_name] + return stream.get_log_events(log_group_name, log_stream_name, start_time, end_time, limit, next_token, start_from_head) + + def filter_log_events(self, log_group_name, log_stream_names, start_time, end_time, limit, next_token, filter_pattern, interleaved): + assert not filter_pattern # TODO: impl + + streams = [stream for name, stream in self.streams.items() if not log_stream_names or name in log_stream_names] + + events = [] + for stream in streams: + events += stream.filter_log_events(log_group_name, log_stream_names, start_time, end_time, limit, next_token, filter_pattern, interleaved) + + if interleaved: + events = sorted(events, key=lambda event: event.timestamp) + + if next_token is None: + next_token = 0 + + events_page = events[next_token: next_token + limit] + next_token += limit + if next_token >= len(events): + next_token = None + + searched_streams = [{"logStreamName": stream.logStreamName, "searchedCompletely": True} for stream in streams] + return events_page, next_token, searched_streams + + +class LogsBackend(BaseBackend): + def __init__(self, region_name): + self.region_name = region_name + self.groups = dict() # { logGroupName: LogGroup} + + def reset(self): + region_name = self.region_name + self.__dict__ = {} + self.__init__(region_name) + + def create_log_group(self, log_group_name, tags): + assert log_group_name not in self.groups + self.groups[log_group_name] = LogGroup(self.region_name, log_group_name, tags) + + def ensure_log_group(self, log_group_name, tags): + if log_group_name in self.groups: + return + self.groups[log_group_name] = LogGroup(self.region_name, log_group_name, tags) + + def delete_log_group(self, log_group_name): + assert log_group_name in self.groups + del self.groups[log_group_name] + + def create_log_stream(self, log_group_name, log_stream_name): + assert log_group_name in self.groups + log_group = self.groups[log_group_name] + return log_group.create_log_stream(log_stream_name) + + def delete_log_stream(self, log_group_name, log_stream_name): + assert log_group_name in self.groups + log_group = self.groups[log_group_name] + return log_group.delete_log_stream(log_stream_name) + + def describe_log_streams(self, descending, limit, log_group_name, log_stream_name_prefix, next_token, order_by): + assert log_group_name in self.groups + log_group = self.groups[log_group_name] + return log_group.describe_log_streams(descending, limit, log_group_name, log_stream_name_prefix, next_token, order_by) + + def put_log_events(self, log_group_name, log_stream_name, log_events, sequence_token): + # TODO: add support for sequence_tokens + assert log_group_name in self.groups + log_group = self.groups[log_group_name] + return log_group.put_log_events(log_group_name, log_stream_name, log_events, sequence_token) + + def get_log_events(self, log_group_name, log_stream_name, start_time, end_time, limit, next_token, start_from_head): + assert log_group_name in self.groups + log_group = self.groups[log_group_name] + return log_group.get_log_events(log_group_name, log_stream_name, start_time, end_time, limit, next_token, start_from_head) + + def filter_log_events(self, log_group_name, log_stream_names, start_time, end_time, limit, next_token, filter_pattern, interleaved): + assert log_group_name in self.groups + log_group = self.groups[log_group_name] + return log_group.filter_log_events(log_group_name, log_stream_names, start_time, end_time, limit, next_token, filter_pattern, interleaved) + + +logs_backends = {region.name: LogsBackend(region.name) for region in boto.logs.regions()} diff --git a/moto/logs/responses.py b/moto/logs/responses.py new file mode 100644 index 000000000..4cb9caa6a --- /dev/null +++ b/moto/logs/responses.py @@ -0,0 +1,114 @@ +from moto.core.responses import BaseResponse +from .models import logs_backends +import json + + +# See http://docs.aws.amazon.com/AmazonCloudWatchLogs/latest/APIReference/Welcome.html + +class LogsResponse(BaseResponse): + @property + def logs_backend(self): + return logs_backends[self.region] + + @property + def request_params(self): + try: + return json.loads(self.body) + except ValueError: + return {} + + def _get_param(self, param, if_none=None): + return self.request_params.get(param, if_none) + + def create_log_group(self): + log_group_name = self._get_param('logGroupName') + tags = self._get_param('tags') + assert 1 <= len(log_group_name) <= 512 # TODO: assert pattern + + self.logs_backend.create_log_group(log_group_name, tags) + return '' + + def delete_log_group(self): + log_group_name = self._get_param('logGroupName') + self.logs_backend.delete_log_group(log_group_name) + return '' + + def create_log_stream(self): + log_group_name = self._get_param('logGroupName') + log_stream_name = self._get_param('logStreamName') + self.logs_backend.create_log_stream(log_group_name, log_stream_name) + return '' + + def delete_log_stream(self): + log_group_name = self._get_param('logGroupName') + log_stream_name = self._get_param('logStreamName') + self.logs_backend.delete_log_stream(log_group_name, log_stream_name) + return '' + + def describe_log_streams(self): + log_group_name = self._get_param('logGroupName') + log_stream_name_prefix = self._get_param('logStreamNamePrefix') + descending = self._get_param('descending', False) + limit = self._get_param('limit', 50) + assert limit <= 50 + next_token = self._get_param('nextToken') + order_by = self._get_param('orderBy', 'LogStreamName') + assert order_by in {'LogStreamName', 'LastEventTime'} + + if order_by == 'LastEventTime': + assert not log_stream_name_prefix + + streams, next_token = self.logs_backend.describe_log_streams( + descending, limit, log_group_name, log_stream_name_prefix, + next_token, order_by) + return json.dumps({ + "logStreams": streams, + "nextToken": next_token + }) + + def put_log_events(self): + log_group_name = self._get_param('logGroupName') + log_stream_name = self._get_param('logStreamName') + log_events = self._get_param('logEvents') + sequence_token = self._get_param('sequenceToken') + + next_sequence_token = self.logs_backend.put_log_events(log_group_name, log_stream_name, log_events, sequence_token) + return json.dumps({'nextSequenceToken': next_sequence_token}) + + def get_log_events(self): + log_group_name = self._get_param('logGroupName') + log_stream_name = self._get_param('logStreamName') + start_time = self._get_param('startTime') + end_time = self._get_param("endTime") + limit = self._get_param('limit', 10000) + assert limit <= 10000 + next_token = self._get_param('nextToken') + start_from_head = self._get_param('startFromHead') + + events, next_backward_token, next_foward_token = \ + self.logs_backend.get_log_events(log_group_name, log_stream_name, start_time, end_time, limit, next_token, start_from_head) + + return json.dumps({ + "events": events, + "nextBackwardToken": next_backward_token, + "nextForwardToken": next_foward_token + }) + + def filter_log_events(self): + log_group_name = self._get_param('logGroupName') + log_stream_names = self._get_param('logStreamNames', []) + start_time = self._get_param('startTime') + # impl, see: http://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/FilterAndPatternSyntax.html + filter_pattern = self._get_param('filterPattern') + interleaved = self._get_param('interleaved', False) + end_time = self._get_param("endTime") + limit = self._get_param('limit', 10000) + assert limit <= 10000 + next_token = self._get_param('nextToken') + + events, next_token, searched_streams = self.logs_backend.filter_log_events(log_group_name, log_stream_names, start_time, end_time, limit, next_token, filter_pattern, interleaved) + return json.dumps({ + "events": events, + "nextToken": next_token, + "searchedLogStreams": searched_streams + }) diff --git a/moto/logs/urls.py b/moto/logs/urls.py new file mode 100644 index 000000000..b7910e675 --- /dev/null +++ b/moto/logs/urls.py @@ -0,0 +1,9 @@ +from .responses import LogsResponse + +url_bases = [ + "https?://logs.(.+).amazonaws.com", +] + +url_paths = { + '{0}/$': LogsResponse.dispatch, +} diff --git a/moto/opsworks/models.py b/moto/opsworks/models.py index 3adfd3323..fe8c882a7 100644 --- a/moto/opsworks/models.py +++ b/moto/opsworks/models.py @@ -422,11 +422,11 @@ class OpsWorksBackend(BaseBackend): stackid = kwargs['stack_id'] if stackid not in self.stacks: raise ResourceNotFoundException(stackid) - if name in [l.name for l in self.layers.values()]: + if name in [l.name for l in self.stacks[stackid].layers]: raise ValidationException( 'There is already a layer named "{0}" ' 'for this stack'.format(name)) - if shortname in [l.shortname for l in self.layers.values()]: + if shortname in [l.shortname for l in self.stacks[stackid].layers]: raise ValidationException( 'There is already a layer with shortname "{0}" ' 'for this stack'.format(shortname)) diff --git a/moto/polly/__init__.py b/moto/polly/__init__.py new file mode 100644 index 000000000..9c2281126 --- /dev/null +++ b/moto/polly/__init__.py @@ -0,0 +1,6 @@ +from __future__ import unicode_literals +from .models import polly_backends +from ..core.models import base_decorator + +polly_backend = polly_backends['us-east-1'] +mock_polly = base_decorator(polly_backends) diff --git a/moto/polly/models.py b/moto/polly/models.py new file mode 100644 index 000000000..e7b7117dc --- /dev/null +++ b/moto/polly/models.py @@ -0,0 +1,114 @@ +from __future__ import unicode_literals +from xml.etree import ElementTree as ET +import datetime + +import boto3 +from moto.core import BaseBackend, BaseModel + +from .resources import VOICE_DATA +from .utils import make_arn_for_lexicon + +DEFAULT_ACCOUNT_ID = 123456789012 + + +class Lexicon(BaseModel): + def __init__(self, name, content, region_name): + self.name = name + self.content = content + self.size = 0 + self.alphabet = None + self.last_modified = None + self.language_code = None + self.lexemes_count = 0 + self.arn = make_arn_for_lexicon(DEFAULT_ACCOUNT_ID, name, region_name) + + self.update() + + def update(self, content=None): + if content is not None: + self.content = content + + # Probably a very naive approach, but it'll do for now. + try: + root = ET.fromstring(self.content) + self.size = len(self.content) + self.last_modified = int((datetime.datetime.now() - + datetime.datetime(1970, 1, 1)).total_seconds()) + self.lexemes_count = len(root.findall('.')) + + for key, value in root.attrib.items(): + if key.endswith('alphabet'): + self.alphabet = value + elif key.endswith('lang'): + self.language_code = value + + except Exception as err: + raise ValueError('Failure parsing XML: {0}'.format(err)) + + def to_dict(self): + return { + 'Attributes': { + 'Alphabet': self.alphabet, + 'LanguageCode': self.language_code, + 'LastModified': self.last_modified, + 'LexemesCount': self.lexemes_count, + 'LexiconArn': self.arn, + 'Size': self.size + } + } + + def __repr__(self): + return ''.format(self.name) + + +class PollyBackend(BaseBackend): + def __init__(self, region_name=None): + super(PollyBackend, self).__init__() + self.region_name = region_name + + self._lexicons = {} + + def reset(self): + region_name = self.region_name + self.__dict__ = {} + self.__init__(region_name) + + def describe_voices(self, language_code, next_token): + if language_code is None: + return VOICE_DATA + + return [item for item in VOICE_DATA if item['LanguageCode'] == language_code] + + def delete_lexicon(self, name): + # implement here + del self._lexicons[name] + + def get_lexicon(self, name): + # Raises KeyError + return self._lexicons[name] + + def list_lexicons(self, next_token): + + result = [] + + for name, lexicon in self._lexicons.items(): + lexicon_dict = lexicon.to_dict() + lexicon_dict['Name'] = name + + result.append(lexicon_dict) + + return result + + def put_lexicon(self, name, content): + # If lexicon content is bad, it will raise ValueError + if name in self._lexicons: + # Regenerated all the stats from the XML + # but keeps the ARN + self._lexicons.update(content) + else: + lexicon = Lexicon(name, content, region_name=self.region_name) + 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} diff --git a/moto/polly/resources.py b/moto/polly/resources.py new file mode 100644 index 000000000..f4ad69a98 --- /dev/null +++ b/moto/polly/resources.py @@ -0,0 +1,63 @@ +# -*- coding: utf-8 -*- + +VOICE_DATA = [ + {'Id': 'Joanna', 'LanguageCode': 'en-US', 'LanguageName': 'US English', 'Gender': 'Female', 'Name': 'Joanna'}, + {'Id': 'Mizuki', 'LanguageCode': 'ja-JP', 'LanguageName': 'Japanese', 'Gender': 'Female', 'Name': 'Mizuki'}, + {'Id': 'Filiz', 'LanguageCode': 'tr-TR', 'LanguageName': 'Turkish', 'Gender': 'Female', 'Name': 'Filiz'}, + {'Id': 'Astrid', 'LanguageCode': 'sv-SE', 'LanguageName': 'Swedish', 'Gender': 'Female', 'Name': 'Astrid'}, + {'Id': 'Tatyana', 'LanguageCode': 'ru-RU', 'LanguageName': 'Russian', 'Gender': 'Female', 'Name': 'Tatyana'}, + {'Id': 'Maxim', 'LanguageCode': 'ru-RU', 'LanguageName': 'Russian', 'Gender': 'Male', 'Name': 'Maxim'}, + {'Id': 'Carmen', 'LanguageCode': 'ro-RO', 'LanguageName': 'Romanian', 'Gender': 'Female', 'Name': 'Carmen'}, + {'Id': 'Ines', 'LanguageCode': 'pt-PT', 'LanguageName': 'Portuguese', 'Gender': 'Female', 'Name': 'Inês'}, + {'Id': 'Cristiano', 'LanguageCode': 'pt-PT', 'LanguageName': 'Portuguese', 'Gender': 'Male', 'Name': 'Cristiano'}, + {'Id': 'Vitoria', 'LanguageCode': 'pt-BR', 'LanguageName': 'Brazilian Portuguese', 'Gender': 'Female', 'Name': 'Vitória'}, + {'Id': 'Ricardo', 'LanguageCode': 'pt-BR', 'LanguageName': 'Brazilian Portuguese', 'Gender': 'Male', 'Name': 'Ricardo'}, + {'Id': 'Maja', 'LanguageCode': 'pl-PL', 'LanguageName': 'Polish', 'Gender': 'Female', 'Name': 'Maja'}, + {'Id': 'Jan', 'LanguageCode': 'pl-PL', 'LanguageName': 'Polish', 'Gender': 'Male', 'Name': 'Jan'}, + {'Id': 'Ewa', 'LanguageCode': 'pl-PL', 'LanguageName': 'Polish', 'Gender': 'Female', 'Name': 'Ewa'}, + {'Id': 'Ruben', 'LanguageCode': 'nl-NL', 'LanguageName': 'Dutch', 'Gender': 'Male', 'Name': 'Ruben'}, + {'Id': 'Lotte', 'LanguageCode': 'nl-NL', 'LanguageName': 'Dutch', 'Gender': 'Female', 'Name': 'Lotte'}, + {'Id': 'Liv', 'LanguageCode': 'nb-NO', 'LanguageName': 'Norwegian', 'Gender': 'Female', 'Name': 'Liv'}, + {'Id': 'Giorgio', 'LanguageCode': 'it-IT', 'LanguageName': 'Italian', 'Gender': 'Male', 'Name': 'Giorgio'}, + {'Id': 'Carla', 'LanguageCode': 'it-IT', 'LanguageName': 'Italian', 'Gender': 'Female', 'Name': 'Carla'}, + {'Id': 'Karl', 'LanguageCode': 'is-IS', 'LanguageName': 'Icelandic', 'Gender': 'Male', 'Name': 'Karl'}, + {'Id': 'Dora', 'LanguageCode': 'is-IS', 'LanguageName': 'Icelandic', 'Gender': 'Female', 'Name': 'Dóra'}, + {'Id': 'Mathieu', 'LanguageCode': 'fr-FR', 'LanguageName': 'French', 'Gender': 'Male', 'Name': 'Mathieu'}, + {'Id': 'Celine', 'LanguageCode': 'fr-FR', 'LanguageName': 'French', 'Gender': 'Female', 'Name': 'Céline'}, + {'Id': 'Chantal', 'LanguageCode': 'fr-CA', 'LanguageName': 'Canadian French', 'Gender': 'Female', 'Name': 'Chantal'}, + {'Id': 'Penelope', 'LanguageCode': 'es-US', 'LanguageName': 'US Spanish', 'Gender': 'Female', 'Name': 'Penélope'}, + {'Id': 'Miguel', 'LanguageCode': 'es-US', 'LanguageName': 'US Spanish', 'Gender': 'Male', 'Name': 'Miguel'}, + {'Id': 'Enrique', 'LanguageCode': 'es-ES', 'LanguageName': 'Castilian Spanish', 'Gender': 'Male', 'Name': 'Enrique'}, + {'Id': 'Conchita', 'LanguageCode': 'es-ES', 'LanguageName': 'Castilian Spanish', 'Gender': 'Female', 'Name': 'Conchita'}, + {'Id': 'Geraint', 'LanguageCode': 'en-GB-WLS', 'LanguageName': 'Welsh English', 'Gender': 'Male', 'Name': 'Geraint'}, + {'Id': 'Salli', 'LanguageCode': 'en-US', 'LanguageName': 'US English', 'Gender': 'Female', 'Name': 'Salli'}, + {'Id': 'Kimberly', 'LanguageCode': 'en-US', 'LanguageName': 'US English', 'Gender': 'Female', 'Name': 'Kimberly'}, + {'Id': 'Kendra', 'LanguageCode': 'en-US', 'LanguageName': 'US English', 'Gender': 'Female', 'Name': 'Kendra'}, + {'Id': 'Justin', 'LanguageCode': 'en-US', 'LanguageName': 'US English', 'Gender': 'Male', 'Name': 'Justin'}, + {'Id': 'Joey', 'LanguageCode': 'en-US', 'LanguageName': 'US English', 'Gender': 'Male', 'Name': 'Joey'}, + {'Id': 'Ivy', 'LanguageCode': 'en-US', 'LanguageName': 'US English', 'Gender': 'Female', 'Name': 'Ivy'}, + {'Id': 'Raveena', 'LanguageCode': 'en-IN', 'LanguageName': 'Indian English', 'Gender': 'Female', 'Name': 'Raveena'}, + {'Id': 'Emma', 'LanguageCode': 'en-GB', 'LanguageName': 'British English', 'Gender': 'Female', 'Name': 'Emma'}, + {'Id': 'Brian', 'LanguageCode': 'en-GB', 'LanguageName': 'British English', 'Gender': 'Male', 'Name': 'Brian'}, + {'Id': 'Amy', 'LanguageCode': 'en-GB', 'LanguageName': 'British English', 'Gender': 'Female', 'Name': 'Amy'}, + {'Id': 'Russell', 'LanguageCode': 'en-AU', 'LanguageName': 'Australian English', 'Gender': 'Male', 'Name': 'Russell'}, + {'Id': 'Nicole', 'LanguageCode': 'en-AU', 'LanguageName': 'Australian English', 'Gender': 'Female', 'Name': 'Nicole'}, + {'Id': 'Vicki', 'LanguageCode': 'de-DE', 'LanguageName': 'German', 'Gender': 'Female', 'Name': 'Vicki'}, + {'Id': 'Marlene', 'LanguageCode': 'de-DE', 'LanguageName': 'German', 'Gender': 'Female', 'Name': 'Marlene'}, + {'Id': 'Hans', 'LanguageCode': 'de-DE', 'LanguageName': 'German', 'Gender': 'Male', 'Name': 'Hans'}, + {'Id': 'Naja', 'LanguageCode': 'da-DK', 'LanguageName': 'Danish', 'Gender': 'Female', 'Name': 'Naja'}, + {'Id': 'Mads', 'LanguageCode': 'da-DK', 'LanguageName': 'Danish', 'Gender': 'Male', 'Name': 'Mads'}, + {'Id': 'Gwyneth', 'LanguageCode': 'cy-GB', 'LanguageName': 'Welsh', 'Gender': 'Female', 'Name': 'Gwyneth'}, + {'Id': 'Jacek', 'LanguageCode': 'pl-PL', 'LanguageName': 'Polish', 'Gender': 'Male', 'Name': 'Jacek'} +] + +# {...} is also shorthand set syntax +LANGUAGE_CODES = {'cy-GB', 'da-DK', 'de-DE', 'en-AU', 'en-GB', 'en-GB-WLS', 'en-IN', 'en-US', 'es-ES', 'es-US', + 'fr-CA', 'fr-FR', 'is-IS', 'it-IT', 'ja-JP', 'nb-NO', 'nl-NL', 'pl-PL', 'pt-BR', 'pt-PT', 'ro-RO', + 'ru-RU', 'sv-SE', 'tr-TR'} + +VOICE_IDS = {'Geraint', 'Gwyneth', 'Mads', 'Naja', 'Hans', 'Marlene', 'Nicole', 'Russell', 'Amy', 'Brian', 'Emma', + 'Raveena', 'Ivy', 'Joanna', 'Joey', 'Justin', 'Kendra', 'Kimberly', 'Salli', 'Conchita', 'Enrique', + 'Miguel', 'Penelope', 'Chantal', 'Celine', 'Mathieu', 'Dora', 'Karl', 'Carla', 'Giorgio', 'Mizuki', + 'Liv', 'Lotte', 'Ruben', 'Ewa', 'Jacek', 'Jan', 'Maja', 'Ricardo', 'Vitoria', 'Cristiano', 'Ines', + 'Carmen', 'Maxim', 'Tatyana', 'Astrid', 'Filiz'} diff --git a/moto/polly/responses.py b/moto/polly/responses.py new file mode 100644 index 000000000..810264424 --- /dev/null +++ b/moto/polly/responses.py @@ -0,0 +1,188 @@ +from __future__ import unicode_literals + +import json +import re + +from six.moves.urllib.parse import urlsplit + +from moto.core.responses import BaseResponse +from .models import polly_backends +from .resources import LANGUAGE_CODES, VOICE_IDS + +LEXICON_NAME_REGEX = re.compile(r'^[0-9A-Za-z]{1,20}$') + + +class PollyResponse(BaseResponse): + @property + def polly_backend(self): + return polly_backends[self.region] + + @property + def json(self): + if not hasattr(self, '_json'): + self._json = json.loads(self.body) + return self._json + + def _error(self, code, message): + return json.dumps({'__type': code, 'message': message}), dict(status=400) + + def _get_action(self): + # Amazon is now naming things /v1/api_name + url_parts = urlsplit(self.uri).path.lstrip('/').split('/') + # [0] = 'v1' + + return url_parts[1] + + # DescribeVoices + def voices(self): + language_code = self._get_param('LanguageCode') + next_token = self._get_param('NextToken') + + if language_code is not None and language_code not in LANGUAGE_CODES: + msg = "1 validation error detected: Value '{0}' at 'languageCode' failed to satisfy constraint: " \ + "Member must satisfy enum value set: [{1}]".format(language_code, ', '.join(LANGUAGE_CODES)) + return msg, dict(status=400) + + voices = self.polly_backend.describe_voices(language_code, next_token) + + return json.dumps({'Voices': voices}) + + def lexicons(self): + # Dish out requests based on methods + + # anything after the /v1/lexicons/ + args = urlsplit(self.uri).path.lstrip('/').split('/')[2:] + + if self.method == 'GET': + if len(args) == 0: + return self._get_lexicons_list() + else: + return self._get_lexicon(*args) + elif self.method == 'PUT': + return self._put_lexicons(*args) + elif self.method == 'DELETE': + return self._delete_lexicon(*args) + + return self._error('InvalidAction', 'Bad route') + + # PutLexicon + def _put_lexicons(self, lexicon_name): + if LEXICON_NAME_REGEX.match(lexicon_name) is None: + return self._error('InvalidParameterValue', 'Lexicon name must match [0-9A-Za-z]{1,20}') + + if 'Content' not in self.json: + return self._error('MissingParameter', 'Content is missing from the body') + + self.polly_backend.put_lexicon(lexicon_name, self.json['Content']) + + return '' + + # ListLexicons + def _get_lexicons_list(self): + next_token = self._get_param('NextToken') + + result = { + 'Lexicons': self.polly_backend.list_lexicons(next_token) + } + + return json.dumps(result) + + # GetLexicon + def _get_lexicon(self, lexicon_name): + try: + lexicon = self.polly_backend.get_lexicon(lexicon_name) + except KeyError: + return self._error('LexiconNotFoundException', 'Lexicon not found') + + result = { + 'Lexicon': { + 'Name': lexicon_name, + 'Content': lexicon.content + }, + 'LexiconAttributes': lexicon.to_dict()['Attributes'] + } + + return json.dumps(result) + + # DeleteLexicon + def _delete_lexicon(self, lexicon_name): + try: + self.polly_backend.delete_lexicon(lexicon_name) + except KeyError: + return self._error('LexiconNotFoundException', 'Lexicon not found') + + return '' + + # SynthesizeSpeech + def speech(self): + # Sanity check params + args = { + 'lexicon_names': None, + 'sample_rate': 22050, + 'speech_marks': None, + 'text': None, + 'text_type': 'text' + } + + if 'LexiconNames' in self.json: + for lex in self.json['LexiconNames']: + try: + self.polly_backend.get_lexicon(lex) + except KeyError: + return self._error('LexiconNotFoundException', 'Lexicon not found') + + args['lexicon_names'] = self.json['LexiconNames'] + + if 'OutputFormat' not in self.json: + return self._error('MissingParameter', 'Missing parameter OutputFormat') + if self.json['OutputFormat'] not in ('json', 'mp3', 'ogg_vorbis', 'pcm'): + return self._error('InvalidParameterValue', 'Not one of json, mp3, ogg_vorbis, pcm') + args['output_format'] = self.json['OutputFormat'] + + if 'SampleRate' in self.json: + sample_rate = int(self.json['SampleRate']) + if sample_rate not in (8000, 16000, 22050): + return self._error('InvalidSampleRateException', 'The specified sample rate is not valid.') + args['sample_rate'] = sample_rate + + if 'SpeechMarkTypes' in self.json: + for value in self.json['SpeechMarkTypes']: + if value not in ('sentance', 'ssml', 'viseme', 'word'): + return self._error('InvalidParameterValue', 'Not one of sentance, ssml, viseme, word') + args['speech_marks'] = self.json['SpeechMarkTypes'] + + if 'Text' not in self.json: + return self._error('MissingParameter', 'Missing parameter Text') + args['text'] = self.json['Text'] + + if 'TextType' in self.json: + if self.json['TextType'] not in ('ssml', 'text'): + return self._error('InvalidParameterValue', 'Not one of ssml, text') + args['text_type'] = self.json['TextType'] + + if 'VoiceId' not in self.json: + return self._error('MissingParameter', 'Missing parameter VoiceId') + if self.json['VoiceId'] not in VOICE_IDS: + return self._error('InvalidParameterValue', 'Not one of {0}'.format(', '.join(VOICE_IDS))) + args['voice_id'] = self.json['VoiceId'] + + # More validation + if len(args['text']) > 3000: + return self._error('TextLengthExceededException', 'Text too long') + + if args['speech_marks'] is not None and args['output_format'] != 'json': + return self._error('MarksNotSupportedForFormatException', 'OutputFormat must be json') + if args['speech_marks'] is not None and args['text_type'] == 'text': + return self._error('SsmlMarksNotSupportedForTextTypeException', 'TextType must be ssml') + + content_type = 'audio/json' + if args['output_format'] == 'mp3': + content_type = 'audio/mpeg' + elif args['output_format'] == 'ogg_vorbis': + content_type = 'audio/ogg' + elif args['output_format'] == 'pcm': + content_type = 'audio/pcm' + + headers = {'Content-Type': content_type} + + return '\x00\x00\x00\x00\x00\x00\x00\x00', headers diff --git a/moto/polly/urls.py b/moto/polly/urls.py new file mode 100644 index 000000000..bd4057a0b --- /dev/null +++ b/moto/polly/urls.py @@ -0,0 +1,13 @@ +from __future__ import unicode_literals +from .responses import PollyResponse + +url_bases = [ + "https?://polly.(.+).amazonaws.com", +] + +url_paths = { + '{0}/v1/voices': PollyResponse.dispatch, + '{0}/v1/lexicons/(?P[^/]+)': PollyResponse.dispatch, + '{0}/v1/lexicons': PollyResponse.dispatch, + '{0}/v1/speech': PollyResponse.dispatch, +} diff --git a/moto/polly/utils.py b/moto/polly/utils.py new file mode 100644 index 000000000..253b19e13 --- /dev/null +++ b/moto/polly/utils.py @@ -0,0 +1,5 @@ +from __future__ import unicode_literals + + +def make_arn_for_lexicon(account_id, name, region_name): + return "arn:aws:polly:{0}:{1}:lexicon/{2}".format(region_name, account_id, name) diff --git a/moto/rds2/exceptions.py b/moto/rds2/exceptions.py index 057a13ba2..0e716310e 100644 --- a/moto/rds2/exceptions.py +++ b/moto/rds2/exceptions.py @@ -58,3 +58,36 @@ class DBParameterGroupNotFoundError(RDSClientError): super(DBParameterGroupNotFoundError, self).__init__( 'DBParameterGroupNotFound', 'DB Parameter Group {0} not found.'.format(db_parameter_group_name)) + + +class InvalidDBClusterStateFaultError(RDSClientError): + + def __init__(self, database_identifier): + super(InvalidDBClusterStateFaultError, self).__init__( + 'InvalidDBClusterStateFault', + 'Invalid DB type, when trying to perform StopDBInstance on {0}e. See AWS RDS documentation on rds.stop_db_instance'.format(database_identifier)) + + +class InvalidDBInstanceStateError(RDSClientError): + + def __init__(self, database_identifier, istate): + estate = "in available state" if istate == 'stop' else "stopped, it cannot be started" + super(InvalidDBInstanceStateError, self).__init__( + 'InvalidDBInstanceState', + 'Instance {} is not {}.'.format(database_identifier, estate)) + + +class SnapshotQuotaExceededError(RDSClientError): + + def __init__(self): + super(SnapshotQuotaExceededError, self).__init__( + 'SnapshotQuotaExceeded', + 'The request cannot be processed because it would exceed the maximum number of snapshots.') + + +class DBSnapshotAlreadyExistsError(RDSClientError): + + def __init__(self, database_snapshot_identifier): + super(DBSnapshotAlreadyExistsError, self).__init__( + 'DBSnapshotAlreadyExists', + 'Cannot create the snapshot because a snapshot with the identifier {} already exists.'.format(database_snapshot_identifier)) diff --git a/moto/rds2/models.py b/moto/rds2/models.py index 5abd2ed1b..bb66ead57 100644 --- a/moto/rds2/models.py +++ b/moto/rds2/models.py @@ -2,6 +2,7 @@ from __future__ import unicode_literals import copy import datetime +import os from collections import defaultdict import boto.rds2 @@ -18,7 +19,11 @@ from .exceptions import (RDSClientError, DBSnapshotNotFoundError, DBSecurityGroupNotFoundError, DBSubnetGroupNotFoundError, - DBParameterGroupNotFoundError) + DBParameterGroupNotFoundError, + InvalidDBClusterStateFaultError, + InvalidDBInstanceStateError, + SnapshotQuotaExceededError, + DBSnapshotAlreadyExistsError) class Database(BaseModel): @@ -674,10 +679,14 @@ class RDS2Backend(BaseBackend): self.databases[database_id] = database return database - def create_snapshot(self, db_instance_identifier, db_snapshot_identifier, tags): + def create_snapshot(self, db_instance_identifier, db_snapshot_identifier, tags=None): database = self.databases.get(db_instance_identifier) if not database: raise DBInstanceNotFoundError(db_instance_identifier) + if db_snapshot_identifier in self.snapshots: + raise DBSnapshotAlreadyExistsError(db_snapshot_identifier) + if len(self.snapshots) >= int(os.environ.get('MOTO_RDS_SNAPSHOT_LIMIT', '100')): + raise SnapshotQuotaExceededError() snapshot = Snapshot(database, db_snapshot_identifier, tags) self.snapshots[db_snapshot_identifier] = snapshot return snapshot @@ -733,6 +742,27 @@ class RDS2Backend(BaseBackend): database = self.describe_databases(db_instance_identifier)[0] return database + def stop_database(self, db_instance_identifier, db_snapshot_identifier=None): + database = self.describe_databases(db_instance_identifier)[0] + # todo: certain rds types not allowed to be stopped at this time. + if database.is_replica or database.multi_az: + # todo: more db types not supported by stop/start instance api + raise InvalidDBClusterStateFaultError(db_instance_identifier) + if database.status != 'available': + raise InvalidDBInstanceStateError(db_instance_identifier, 'stop') + if db_snapshot_identifier: + self.create_snapshot(db_instance_identifier, db_snapshot_identifier) + database.status = 'shutdown' + return database + + def start_database(self, db_instance_identifier): + database = self.describe_databases(db_instance_identifier)[0] + # todo: bunch of different error messages to be generated from this api call + if database.status != 'shutdown': + raise InvalidDBInstanceStateError(db_instance_identifier, 'start') + database.status = 'available' + return database + def find_db_from_id(self, db_id): if self.arn_regex.match(db_id): arn_breakdown = db_id.split(':') diff --git a/moto/rds2/responses.py b/moto/rds2/responses.py index ef02bfbf1..bf76660aa 100644 --- a/moto/rds2/responses.py +++ b/moto/rds2/responses.py @@ -23,6 +23,7 @@ class RDS2Response(BaseResponse): "db_instance_identifier": self._get_param('DBInstanceIdentifier'), "db_name": self._get_param("DBName"), "db_parameter_group_name": self._get_param("DBParameterGroupName"), + "db_snapshot_identifier": self._get_param('DBSnapshotIdentifier'), "db_subnet_group_name": self._get_param("DBSubnetGroupName"), "engine": self._get_param("Engine"), "engine_version": self._get_param("EngineVersion"), @@ -193,6 +194,19 @@ class RDS2Response(BaseResponse): template = self.response_template(REMOVE_TAGS_FROM_RESOURCE_TEMPLATE) return template.render() + def stop_db_instance(self): + db_instance_identifier = self._get_param('DBInstanceIdentifier') + db_snapshot_identifier = self._get_param('DBSnapshotIdentifier') + database = self.backend.stop_database(db_instance_identifier, db_snapshot_identifier) + template = self.response_template(STOP_DATABASE_TEMPLATE) + return template.render(database=database) + + def start_db_instance(self): + db_instance_identifier = self._get_param('DBInstanceIdentifier') + database = self.backend.start_database(db_instance_identifier) + template = self.response_template(START_DATABASE_TEMPLATE) + return template.render(database=database) + def create_db_security_group(self): group_name = self._get_param('DBSecurityGroupName') description = self._get_param('DBSecurityGroupDescription') @@ -410,6 +424,23 @@ REBOOT_DATABASE_TEMPLATE = """ + + {{ database.to_xml() }} + + + 523e3218-afc7-11c3-90f5-f90431260ab9 + +""" + +STOP_DATABASE_TEMPLATE = """ + + {{ database.to_xml() }} + + + 523e3218-afc7-11c3-90f5-f90431260ab8 + +""" DELETE_DATABASE_TEMPLATE = """ diff --git a/moto/redshift/exceptions.py b/moto/redshift/exceptions.py index 877e850e4..a89ed5a04 100644 --- a/moto/redshift/exceptions.py +++ b/moto/redshift/exceptions.py @@ -71,3 +71,25 @@ class ClusterSnapshotAlreadyExistsError(RedshiftClientError): 'ClusterSnapshotAlreadyExists', "Cannot create the snapshot because a snapshot with the " "identifier {0} already exists".format(snapshot_identifier)) + + +class InvalidParameterValueError(RedshiftClientError): + def __init__(self, message): + super(InvalidParameterValueError, self).__init__( + 'InvalidParameterValue', + message) + + +class ResourceNotFoundFaultError(RedshiftClientError): + + code = 404 + + def __init__(self, resource_type=None, resource_name=None, message=None): + if resource_type and not resource_name: + msg = "resource of type '{0}' not found.".format(resource_type) + else: + msg = "{0} ({1}) not found.".format(resource_type, resource_name) + if message: + msg = message + super(ResourceNotFoundFaultError, self).__init__( + 'ResourceNotFoundFault', msg) diff --git a/moto/redshift/models.py b/moto/redshift/models.py index 29c802fb0..fa642ef01 100644 --- a/moto/redshift/models.py +++ b/moto/redshift/models.py @@ -15,11 +15,51 @@ from .exceptions import ( ClusterSnapshotAlreadyExistsError, ClusterSnapshotNotFoundError, ClusterSubnetGroupNotFoundError, + InvalidParameterValueError, InvalidSubnetError, + ResourceNotFoundFaultError ) -class Cluster(BaseModel): +ACCOUNT_ID = 123456789012 + + +class TaggableResourceMixin(object): + + resource_type = None + + def __init__(self, region_name, tags): + self.region = region_name + self.tags = tags or [] + + @property + def resource_id(self): + return None + + @property + def arn(self): + return "arn:aws:redshift:{region}:{account_id}:{resource_type}:{resource_id}".format( + region=self.region, + account_id=ACCOUNT_ID, + resource_type=self.resource_type, + resource_id=self.resource_id) + + def create_tags(self, tags): + new_keys = [tag_set['Key'] for tag_set in tags] + self.tags = [tag_set for tag_set in self.tags + if tag_set['Key'] not in new_keys] + self.tags.extend(tags) + return self.tags + + def delete_tags(self, tag_keys): + self.tags = [tag_set for tag_set in self.tags + if tag_set['Key'] not in tag_keys] + return self.tags + + +class Cluster(TaggableResourceMixin, BaseModel): + + resource_type = 'cluster' def __init__(self, redshift_backend, cluster_identifier, node_type, master_username, master_user_password, db_name, cluster_type, cluster_security_groups, @@ -27,7 +67,8 @@ class Cluster(BaseModel): preferred_maintenance_window, cluster_parameter_group_name, automated_snapshot_retention_period, port, cluster_version, allow_version_upgrade, number_of_nodes, publicly_accessible, - encrypted, region): + encrypted, region_name, tags=None): + super(Cluster, self).__init__(region_name, tags) self.redshift_backend = redshift_backend self.cluster_identifier = cluster_identifier self.status = 'available' @@ -57,13 +98,12 @@ class Cluster(BaseModel): else: self.cluster_security_groups = ["Default"] - self.region = region if availability_zone: self.availability_zone = availability_zone else: # This could probably be smarter, but there doesn't appear to be a # way to pull AZs for a region in boto - self.availability_zone = region + "a" + self.availability_zone = region_name + "a" if cluster_type == 'single-node': self.number_of_nodes = 1 @@ -106,7 +146,7 @@ class Cluster(BaseModel): number_of_nodes=properties.get('NumberOfNodes'), publicly_accessible=properties.get("PubliclyAccessible"), encrypted=properties.get("Encrypted"), - region=region_name, + region_name=region_name, ) return cluster @@ -149,6 +189,10 @@ class Cluster(BaseModel): if parameter_group.cluster_parameter_group_name in self.cluster_parameter_group_name ] + @property + def resource_id(self): + return self.cluster_identifier + def to_json(self): return { "MasterUsername": self.master_username, @@ -180,18 +224,21 @@ class Cluster(BaseModel): "ClusterIdentifier": self.cluster_identifier, "AllowVersionUpgrade": self.allow_version_upgrade, "Endpoint": { - "Address": '{}.{}.redshift.amazonaws.com'.format( - self.cluster_identifier, - self.region), + "Address": self.endpoint, "Port": self.port }, - "PendingModifiedValues": [] + "PendingModifiedValues": [], + "Tags": self.tags } -class SubnetGroup(BaseModel): +class SubnetGroup(TaggableResourceMixin, BaseModel): - def __init__(self, ec2_backend, cluster_subnet_group_name, description, subnet_ids): + resource_type = 'subnetgroup' + + def __init__(self, ec2_backend, cluster_subnet_group_name, description, subnet_ids, + region_name, tags=None): + super(SubnetGroup, self).__init__(region_name, tags) self.ec2_backend = ec2_backend self.cluster_subnet_group_name = cluster_subnet_group_name self.description = description @@ -208,6 +255,7 @@ class SubnetGroup(BaseModel): cluster_subnet_group_name=resource_name, description=properties.get("Description"), subnet_ids=properties.get("SubnetIds", []), + region_name=region_name ) return subnet_group @@ -219,6 +267,10 @@ class SubnetGroup(BaseModel): def vpc_id(self): return self.subnets[0].vpc_id + @property + def resource_id(self): + return self.cluster_subnet_group_name + def to_json(self): return { "VpcId": self.vpc_id, @@ -232,27 +284,39 @@ class SubnetGroup(BaseModel): "Name": subnet.availability_zone }, } for subnet in self.subnets], + "Tags": self.tags } -class SecurityGroup(BaseModel): +class SecurityGroup(TaggableResourceMixin, BaseModel): - def __init__(self, cluster_security_group_name, description): + resource_type = 'securitygroup' + + def __init__(self, cluster_security_group_name, description, region_name, tags=None): + super(SecurityGroup, self).__init__(region_name, tags) self.cluster_security_group_name = cluster_security_group_name self.description = description + @property + def resource_id(self): + return self.cluster_security_group_name + def to_json(self): return { "EC2SecurityGroups": [], "IPRanges": [], "Description": self.description, "ClusterSecurityGroupName": self.cluster_security_group_name, + "Tags": self.tags } -class ParameterGroup(BaseModel): +class ParameterGroup(TaggableResourceMixin, BaseModel): - def __init__(self, cluster_parameter_group_name, group_family, description): + resource_type = 'parametergroup' + + def __init__(self, cluster_parameter_group_name, group_family, description, region_name, tags=None): + super(ParameterGroup, self).__init__(region_name, tags) self.cluster_parameter_group_name = cluster_parameter_group_name self.group_family = group_family self.description = description @@ -266,34 +330,41 @@ class ParameterGroup(BaseModel): cluster_parameter_group_name=resource_name, description=properties.get("Description"), group_family=properties.get("ParameterGroupFamily"), + region_name=region_name ) return parameter_group + @property + def resource_id(self): + return self.cluster_parameter_group_name + def to_json(self): return { "ParameterGroupFamily": self.group_family, "Description": self.description, "ParameterGroupName": self.cluster_parameter_group_name, + "Tags": self.tags } -class Snapshot(BaseModel): +class Snapshot(TaggableResourceMixin, BaseModel): - def __init__(self, cluster, snapshot_identifier, tags=None): + resource_type = 'snapshot' + + def __init__(self, cluster, snapshot_identifier, region_name, tags=None): + super(Snapshot, self).__init__(region_name, tags) self.cluster = copy.copy(cluster) self.snapshot_identifier = snapshot_identifier self.snapshot_type = 'manual' self.status = 'available' - self.tags = tags or [] self.create_time = iso_8601_datetime_with_milliseconds( datetime.datetime.now()) @property - def arn(self): - return "arn:aws:redshift:{0}:1234567890:snapshot:{1}/{2}".format( - self.cluster.region, - self.cluster.cluster_identifier, - self.snapshot_identifier) + def resource_id(self): + return "{cluster_id}/{snapshot_id}".format( + cluster_id=self.cluster.cluster_identifier, + snapshot_id=self.snapshot_identifier) def to_json(self): return { @@ -315,26 +386,36 @@ class Snapshot(BaseModel): class RedshiftBackend(BaseBackend): - def __init__(self, ec2_backend): + def __init__(self, ec2_backend, region_name): + self.region = region_name self.clusters = {} self.subnet_groups = {} self.security_groups = { - "Default": SecurityGroup("Default", "Default Redshift Security Group") + "Default": SecurityGroup("Default", "Default Redshift Security Group", self.region) } self.parameter_groups = { "default.redshift-1.0": ParameterGroup( "default.redshift-1.0", "redshift-1.0", "Default Redshift parameter group", + self.region ) } self.ec2_backend = ec2_backend self.snapshots = OrderedDict() + self.RESOURCE_TYPE_MAP = { + 'cluster': self.clusters, + 'parametergroup': self.parameter_groups, + 'securitygroup': self.security_groups, + 'snapshot': self.snapshots, + 'subnetgroup': self.subnet_groups + } def reset(self): ec2_backend = self.ec2_backend + region_name = self.region self.__dict__ = {} - self.__init__(ec2_backend) + self.__init__(ec2_backend, region_name) def create_cluster(self, **cluster_kwargs): cluster_identifier = cluster_kwargs['cluster_identifier'] @@ -373,9 +454,10 @@ class RedshiftBackend(BaseBackend): return self.clusters.pop(cluster_identifier) raise ClusterNotFoundError(cluster_identifier) - def create_cluster_subnet_group(self, cluster_subnet_group_name, description, subnet_ids): + def create_cluster_subnet_group(self, cluster_subnet_group_name, description, subnet_ids, + region_name, tags=None): subnet_group = SubnetGroup( - self.ec2_backend, cluster_subnet_group_name, description, subnet_ids) + self.ec2_backend, cluster_subnet_group_name, description, subnet_ids, region_name, tags) self.subnet_groups[cluster_subnet_group_name] = subnet_group return subnet_group @@ -393,9 +475,9 @@ class RedshiftBackend(BaseBackend): return self.subnet_groups.pop(subnet_identifier) raise ClusterSubnetGroupNotFoundError(subnet_identifier) - def create_cluster_security_group(self, cluster_security_group_name, description): + def create_cluster_security_group(self, cluster_security_group_name, description, region_name, tags=None): security_group = SecurityGroup( - cluster_security_group_name, description) + cluster_security_group_name, description, region_name, tags) self.security_groups[cluster_security_group_name] = security_group return security_group @@ -414,9 +496,9 @@ class RedshiftBackend(BaseBackend): raise ClusterSecurityGroupNotFoundError(security_group_identifier) def create_cluster_parameter_group(self, cluster_parameter_group_name, - group_family, description): + group_family, description, region_name, tags=None): parameter_group = ParameterGroup( - cluster_parameter_group_name, group_family, description) + cluster_parameter_group_name, group_family, description, region_name, tags) self.parameter_groups[cluster_parameter_group_name] = parameter_group return parameter_group @@ -435,17 +517,17 @@ class RedshiftBackend(BaseBackend): return self.parameter_groups.pop(parameter_group_name) raise ClusterParameterGroupNotFoundError(parameter_group_name) - def create_snapshot(self, cluster_identifier, snapshot_identifier, tags): + def create_cluster_snapshot(self, cluster_identifier, snapshot_identifier, region_name, tags): cluster = self.clusters.get(cluster_identifier) if not cluster: raise ClusterNotFoundError(cluster_identifier) if self.snapshots.get(snapshot_identifier) is not None: raise ClusterSnapshotAlreadyExistsError(snapshot_identifier) - snapshot = Snapshot(cluster, snapshot_identifier, tags) + snapshot = Snapshot(cluster, snapshot_identifier, region_name, tags) self.snapshots[snapshot_identifier] = snapshot return snapshot - def describe_snapshots(self, cluster_identifier, snapshot_identifier): + def describe_cluster_snapshots(self, cluster_identifier=None, snapshot_identifier=None): if cluster_identifier: for snapshot in self.snapshots.values(): if snapshot.cluster.cluster_identifier == cluster_identifier: @@ -459,7 +541,7 @@ class RedshiftBackend(BaseBackend): return self.snapshots.values() - def delete_snapshot(self, snapshot_identifier): + def delete_cluster_snapshot(self, snapshot_identifier): if snapshot_identifier not in self.snapshots: raise ClusterSnapshotNotFoundError(snapshot_identifier) @@ -467,23 +549,105 @@ class RedshiftBackend(BaseBackend): deleted_snapshot.status = 'deleted' return deleted_snapshot - def describe_tags_for_resource_type(self, resource_type): + def restore_from_cluster_snapshot(self, **kwargs): + snapshot_identifier = kwargs.pop('snapshot_identifier') + snapshot = self.describe_cluster_snapshots(snapshot_identifier=snapshot_identifier)[0] + create_kwargs = { + "node_type": snapshot.cluster.node_type, + "master_username": snapshot.cluster.master_username, + "master_user_password": snapshot.cluster.master_user_password, + "db_name": snapshot.cluster.db_name, + "cluster_type": 'multi-node' if snapshot.cluster.number_of_nodes > 1 else 'single-node', + "availability_zone": snapshot.cluster.availability_zone, + "port": snapshot.cluster.port, + "cluster_version": snapshot.cluster.cluster_version, + "number_of_nodes": snapshot.cluster.number_of_nodes, + "encrypted": snapshot.cluster.encrypted, + "tags": snapshot.cluster.tags + } + create_kwargs.update(kwargs) + return self.create_cluster(**create_kwargs) + + def _get_resource_from_arn(self, arn): + try: + arn_breakdown = arn.split(':') + resource_type = arn_breakdown[5] + if resource_type == 'snapshot': + resource_id = arn_breakdown[6].split('/')[1] + else: + resource_id = arn_breakdown[6] + except IndexError: + resource_type = resource_id = arn + resources = self.RESOURCE_TYPE_MAP.get(resource_type) + if resources is None: + message = ( + "Tagging is not supported for this type of resource: '{0}' " + "(the ARN is potentially malformed, please check the ARN " + "documentation for more information)".format(resource_type)) + raise ResourceNotFoundFaultError(message=message) + try: + resource = resources[resource_id] + except KeyError: + raise ResourceNotFoundFaultError(resource_type, resource_id) + else: + return resource + + @staticmethod + def _describe_tags_for_resources(resources): tagged_resources = [] - if resource_type == 'Snapshot': - for snapshot in self.snapshots.values(): - for tag in snapshot.tags: - data = { - 'ResourceName': snapshot.arn, - 'ResourceType': 'snapshot', - 'Tag': { - 'Key': tag['Key'], - 'Value': tag['Value'] - } + for resource in resources: + for tag in resource.tags: + data = { + 'ResourceName': resource.arn, + 'ResourceType': resource.resource_type, + 'Tag': { + 'Key': tag['Key'], + 'Value': tag['Value'] } - tagged_resources.append(data) + } + tagged_resources.append(data) return tagged_resources + def _describe_tags_for_resource_type(self, resource_type): + resources = self.RESOURCE_TYPE_MAP.get(resource_type) + if not resources: + raise ResourceNotFoundFaultError(resource_type=resource_type) + return self._describe_tags_for_resources(resources.values()) + + def _describe_tags_for_resource_name(self, resource_name): + resource = self._get_resource_from_arn(resource_name) + return self._describe_tags_for_resources([resource]) + + def create_tags(self, resource_name, tags): + resource = self._get_resource_from_arn(resource_name) + resource.create_tags(tags) + + def describe_tags(self, resource_name, resource_type): + if resource_name and resource_type: + raise InvalidParameterValueError( + "You cannot filter a list of resources using an Amazon " + "Resource Name (ARN) and a resource type together in the " + "same request. Retry the request using either an ARN or " + "a resource type, but not both.") + if resource_type: + return self._describe_tags_for_resource_type(resource_type.lower()) + if resource_name: + return self._describe_tags_for_resource_name(resource_name) + # If name and type are not specified, return all tagged resources. + # TODO: Implement aws marker pagination + tagged_resources = [] + for resource_type in self.RESOURCE_TYPE_MAP: + try: + tagged_resources += self._describe_tags_for_resource_type(resource_type) + except ResourceNotFoundFaultError: + pass + return tagged_resources + + def delete_tags(self, resource_name, tag_keys): + resource = self._get_resource_from_arn(resource_name) + resource.delete_tags(tag_keys) + redshift_backends = {} for region in boto.redshift.regions(): - redshift_backends[region.name] = RedshiftBackend(ec2_backends[region.name]) + redshift_backends[region.name] = RedshiftBackend(ec2_backends[region.name], region.name) diff --git a/moto/redshift/responses.py b/moto/redshift/responses.py index 411569d01..52ca908e8 100644 --- a/moto/redshift/responses.py +++ b/moto/redshift/responses.py @@ -57,6 +57,33 @@ class RedshiftResponse(BaseResponse): count += 1 return unpacked_list + def unpack_list_params(self, label): + unpacked_list = list() + count = 1 + while self._get_param('{0}.{1}'.format(label, count)): + unpacked_list.append(self._get_param( + '{0}.{1}'.format(label, count))) + count += 1 + return unpacked_list + + def _get_cluster_security_groups(self): + cluster_security_groups = self._get_multi_param('ClusterSecurityGroups.member') + if not cluster_security_groups: + cluster_security_groups = self._get_multi_param('ClusterSecurityGroups.ClusterSecurityGroupName') + return cluster_security_groups + + def _get_vpc_security_group_ids(self): + vpc_security_group_ids = self._get_multi_param('VpcSecurityGroupIds.member') + if not vpc_security_group_ids: + vpc_security_group_ids = self._get_multi_param('VpcSecurityGroupIds.VpcSecurityGroupId') + return vpc_security_group_ids + + def _get_subnet_ids(self): + subnet_ids = self._get_multi_param('SubnetIds.member') + if not subnet_ids: + subnet_ids = self._get_multi_param('SubnetIds.SubnetIdentifier') + return subnet_ids + def create_cluster(self): cluster_kwargs = { "cluster_identifier": self._get_param('ClusterIdentifier'), @@ -65,8 +92,8 @@ class RedshiftResponse(BaseResponse): "master_user_password": self._get_param('MasterUserPassword'), "db_name": self._get_param('DBName'), "cluster_type": self._get_param('ClusterType'), - "cluster_security_groups": self._get_multi_param('ClusterSecurityGroups.member'), - "vpc_security_group_ids": self._get_multi_param('VpcSecurityGroupIds.member'), + "cluster_security_groups": self._get_cluster_security_groups(), + "vpc_security_group_ids": self._get_vpc_security_group_ids(), "cluster_subnet_group_name": self._get_param('ClusterSubnetGroupName'), "availability_zone": self._get_param('AvailabilityZone'), "preferred_maintenance_window": self._get_param('PreferredMaintenanceWindow'), @@ -78,7 +105,8 @@ class RedshiftResponse(BaseResponse): "number_of_nodes": self._get_int_param('NumberOfNodes'), "publicly_accessible": self._get_param("PubliclyAccessible"), "encrypted": self._get_param("Encrypted"), - "region": self.region, + "region_name": self.region, + "tags": self.unpack_complex_list_params('Tags.Tag', ('Key', 'Value')) } cluster = self.redshift_backend.create_cluster(**cluster_kwargs).to_json() cluster['ClusterStatus'] = 'creating' @@ -94,23 +122,8 @@ class RedshiftResponse(BaseResponse): }) def restore_from_cluster_snapshot(self): - snapshot_identifier = self._get_param('SnapshotIdentifier') - snapshots = self.redshift_backend.describe_snapshots( - None, - snapshot_identifier) - snapshot = snapshots[0] - kwargs_from_snapshot = { - "node_type": snapshot.cluster.node_type, - "master_username": snapshot.cluster.master_username, - "master_user_password": snapshot.cluster.master_user_password, - "db_name": snapshot.cluster.db_name, - "cluster_type": 'multi-node' if snapshot.cluster.number_of_nodes > 1 else 'single-node', - "availability_zone": snapshot.cluster.availability_zone, - "port": snapshot.cluster.port, - "cluster_version": snapshot.cluster.cluster_version, - "number_of_nodes": snapshot.cluster.number_of_nodes, - } - kwargs_from_request = { + restore_kwargs = { + "snapshot_identifier": self._get_param('SnapshotIdentifier'), "cluster_identifier": self._get_param('ClusterIdentifier'), "port": self._get_int_param('Port'), "availability_zone": self._get_param('AvailabilityZone'), @@ -121,20 +134,15 @@ class RedshiftResponse(BaseResponse): "publicly_accessible": self._get_param("PubliclyAccessible"), "cluster_parameter_group_name": self._get_param( 'ClusterParameterGroupName'), - "cluster_security_groups": self._get_multi_param( - 'ClusterSecurityGroups.member'), - "vpc_security_group_ids": self._get_multi_param( - 'VpcSecurityGroupIds.member'), + "cluster_security_groups": self._get_cluster_security_groups(), + "vpc_security_group_ids": self._get_vpc_security_group_ids(), "preferred_maintenance_window": self._get_param( 'PreferredMaintenanceWindow'), "automated_snapshot_retention_period": self._get_int_param( 'AutomatedSnapshotRetentionPeriod'), - "region": self.region, - "encrypted": False, + "region_name": self.region, } - kwargs_from_snapshot.update(kwargs_from_request) - cluster_kwargs = kwargs_from_snapshot - cluster = self.redshift_backend.create_cluster(**cluster_kwargs).to_json() + cluster = self.redshift_backend.restore_from_cluster_snapshot(**restore_kwargs).to_json() cluster['ClusterStatus'] = 'creating' return self.get_response({ "RestoreFromClusterSnapshotResponse": { @@ -169,8 +177,8 @@ class RedshiftResponse(BaseResponse): "node_type": self._get_param('NodeType'), "master_user_password": self._get_param('MasterUserPassword'), "cluster_type": self._get_param('ClusterType'), - "cluster_security_groups": self._get_multi_param('ClusterSecurityGroups.member'), - "vpc_security_group_ids": self._get_multi_param('VpcSecurityGroupIds.member'), + "cluster_security_groups": self._get_cluster_security_groups(), + "vpc_security_group_ids": self._get_vpc_security_group_ids(), "cluster_subnet_group_name": self._get_param('ClusterSubnetGroupName'), "preferred_maintenance_window": self._get_param('PreferredMaintenanceWindow'), "cluster_parameter_group_name": self._get_param('ClusterParameterGroupName'), @@ -181,12 +189,6 @@ class RedshiftResponse(BaseResponse): "publicly_accessible": self._get_param("PubliclyAccessible"), "encrypted": self._get_param("Encrypted"), } - # There's a bug in boto3 where the security group ids are not passed - # according to the AWS documentation - if not request_kwargs['vpc_security_group_ids']: - request_kwargs['vpc_security_group_ids'] = self._get_multi_param( - 'VpcSecurityGroupIds.VpcSecurityGroupId') - cluster_kwargs = {} # We only want parameters that were actually passed in, otherwise # we'll stomp all over our cluster metadata with None values. @@ -225,16 +227,15 @@ class RedshiftResponse(BaseResponse): def create_cluster_subnet_group(self): cluster_subnet_group_name = self._get_param('ClusterSubnetGroupName') description = self._get_param('Description') - subnet_ids = self._get_multi_param('SubnetIds.member') - # There's a bug in boto3 where the subnet ids are not passed - # according to the AWS documentation - if not subnet_ids: - subnet_ids = self._get_multi_param('SubnetIds.SubnetIdentifier') + subnet_ids = self._get_subnet_ids() + tags = self.unpack_complex_list_params('Tags.Tag', ('Key', 'Value')) subnet_group = self.redshift_backend.create_cluster_subnet_group( cluster_subnet_group_name=cluster_subnet_group_name, description=description, subnet_ids=subnet_ids, + region_name=self.region, + tags=tags ) return self.get_response({ @@ -280,10 +281,13 @@ class RedshiftResponse(BaseResponse): cluster_security_group_name = self._get_param( 'ClusterSecurityGroupName') description = self._get_param('Description') + tags = self.unpack_complex_list_params('Tags.Tag', ('Key', 'Value')) security_group = self.redshift_backend.create_cluster_security_group( cluster_security_group_name=cluster_security_group_name, description=description, + region_name=self.region, + tags=tags ) return self.get_response({ @@ -331,11 +335,14 @@ class RedshiftResponse(BaseResponse): cluster_parameter_group_name = self._get_param('ParameterGroupName') group_family = self._get_param('ParameterGroupFamily') description = self._get_param('Description') + tags = self.unpack_complex_list_params('Tags.Tag', ('Key', 'Value')) parameter_group = self.redshift_backend.create_cluster_parameter_group( cluster_parameter_group_name, group_family, description, + self.region, + tags ) return self.get_response({ @@ -381,11 +388,12 @@ class RedshiftResponse(BaseResponse): def create_cluster_snapshot(self): cluster_identifier = self._get_param('ClusterIdentifier') snapshot_identifier = self._get_param('SnapshotIdentifier') - tags = self.unpack_complex_list_params( - 'Tags.Tag', ('Key', 'Value')) - snapshot = self.redshift_backend.create_snapshot(cluster_identifier, - snapshot_identifier, - tags) + tags = self.unpack_complex_list_params('Tags.Tag', ('Key', 'Value')) + + snapshot = self.redshift_backend.create_cluster_snapshot(cluster_identifier, + snapshot_identifier, + self.region, + tags) return self.get_response({ 'CreateClusterSnapshotResponse': { "CreateClusterSnapshotResult": { @@ -399,9 +407,9 @@ class RedshiftResponse(BaseResponse): def describe_cluster_snapshots(self): cluster_identifier = self._get_param('ClusterIdentifier') - snapshot_identifier = self._get_param('DBSnapshotIdentifier') - snapshots = self.redshift_backend.describe_snapshots(cluster_identifier, - snapshot_identifier) + snapshot_identifier = self._get_param('SnapshotIdentifier') + snapshots = self.redshift_backend.describe_cluster_snapshots(cluster_identifier, + snapshot_identifier) return self.get_response({ "DescribeClusterSnapshotsResponse": { "DescribeClusterSnapshotsResult": { @@ -415,7 +423,7 @@ class RedshiftResponse(BaseResponse): def delete_cluster_snapshot(self): snapshot_identifier = self._get_param('SnapshotIdentifier') - snapshot = self.redshift_backend.delete_snapshot(snapshot_identifier) + snapshot = self.redshift_backend.delete_cluster_snapshot(snapshot_identifier) return self.get_response({ "DeleteClusterSnapshotResponse": { @@ -428,13 +436,26 @@ class RedshiftResponse(BaseResponse): } }) + def create_tags(self): + resource_name = self._get_param('ResourceName') + tags = self.unpack_complex_list_params('Tags.Tag', ('Key', 'Value')) + + self.redshift_backend.create_tags(resource_name, tags) + + return self.get_response({ + "CreateTagsResponse": { + "ResponseMetadata": { + "RequestId": "384ac68d-3775-11df-8963-01868b7c937a", + } + } + }) + def describe_tags(self): + resource_name = self._get_param('ResourceName') resource_type = self._get_param('ResourceType') - if resource_type != 'Snapshot': - raise NotImplementedError( - "The describe_tags action has not been fully implemented.") - tagged_resources = \ - self.redshift_backend.describe_tags_for_resource_type(resource_type) + + tagged_resources = self.redshift_backend.describe_tags(resource_name, + resource_type) return self.get_response({ "DescribeTagsResponse": { "DescribeTagsResult": { @@ -445,3 +466,17 @@ class RedshiftResponse(BaseResponse): } } }) + + def delete_tags(self): + resource_name = self._get_param('ResourceName') + tag_keys = self.unpack_list_params('TagKeys.TagKey') + + self.redshift_backend.delete_tags(resource_name, tag_keys) + + return self.get_response({ + "DeleteTagsResponse": { + "ResponseMetadata": { + "RequestId": "384ac68d-3775-11df-8963-01868b7c937a", + } + } + }) diff --git a/moto/route53/models.py b/moto/route53/models.py index b823cb915..d12f4ee7a 100644 --- a/moto/route53/models.py +++ b/moto/route53/models.py @@ -2,11 +2,20 @@ from __future__ import unicode_literals from collections import defaultdict +import string +import random import uuid from jinja2 import Template from moto.core import BaseBackend, BaseModel -from moto.core.utils import get_random_hex + + +ROUTE53_ID_CHOICE = string.ascii_uppercase + string.digits + + +def create_route53_zone_id(): + # New ID's look like this Z1RWWTK7Y8UDDQ + return ''.join([random.choice(ROUTE53_ID_CHOICE) for _ in range(0, 15)]) class HealthCheck(BaseModel): @@ -247,7 +256,7 @@ class Route53Backend(BaseBackend): self.resource_tags = defaultdict(dict) def create_hosted_zone(self, name, private_zone, comment=None): - new_id = get_random_hex() + new_id = create_route53_zone_id() new_zone = FakeZone( name, new_id, private_zone=private_zone, comment=comment) self.zones[new_id] = new_zone diff --git a/moto/s3/exceptions.py b/moto/s3/exceptions.py index df817ba78..24704e7ef 100644 --- a/moto/s3/exceptions.py +++ b/moto/s3/exceptions.py @@ -91,3 +91,23 @@ class EntityTooSmall(S3ClientError): "EntityTooSmall", "Your proposed upload is smaller than the minimum allowed object size.", *args, **kwargs) + + +class InvalidRequest(S3ClientError): + code = 400 + + def __init__(self, method, *args, **kwargs): + super(InvalidRequest, self).__init__( + "InvalidRequest", + "Found unsupported HTTP method in CORS config. Unsupported method is {}".format(method), + *args, **kwargs) + + +class MalformedXML(S3ClientError): + code = 400 + + def __init__(self, *args, **kwargs): + super(MalformedXML, self).__init__( + "MalformedXML", + "The XML you provided was not well-formed or did not validate against our published schema", + *args, **kwargs) diff --git a/moto/s3/models.py b/moto/s3/models.py index c1a4fb04d..ae05292f2 100644 --- a/moto/s3/models.py +++ b/moto/s3/models.py @@ -201,10 +201,18 @@ class FakeGrantee(BaseModel): self.uri = uri self.display_name = display_name + def __eq__(self, other): + if not isinstance(other, FakeGrantee): + return False + return self.id == other.id and self.uri == other.uri and self.display_name == other.display_name + @property def type(self): return 'Group' if self.uri else 'CanonicalUser' + def __repr__(self): + return "FakeGrantee(display_name: '{}', id: '{}', uri: '{}')".format(self.display_name, self.id, self.uri) + ALL_USERS_GRANTEE = FakeGrantee( uri='http://acs.amazonaws.com/groups/global/AllUsers') @@ -226,12 +234,28 @@ class FakeGrant(BaseModel): self.grantees = grantees self.permissions = permissions + def __repr__(self): + return "FakeGrant(grantees: {}, permissions: {})".format(self.grantees, self.permissions) + class FakeAcl(BaseModel): def __init__(self, grants=[]): self.grants = grants + @property + def public_read(self): + for grant in self.grants: + if ALL_USERS_GRANTEE in grant.grantees: + if PERMISSION_READ in grant.permissions: + return True + if PERMISSION_FULL_CONTROL in grant.permissions: + return True + return False + + def __repr__(self): + return "FakeAcl(grants: {})".format(self.grants) + def get_canned_acl(acl): owner_grantee = FakeGrantee( @@ -295,6 +319,26 @@ class LifecycleRule(BaseModel): self.storage_class = storage_class +class CorsRule(BaseModel): + + def __init__(self, allowed_methods, allowed_origins, allowed_headers=None, expose_headers=None, + max_age_seconds=None): + # Python 2 and 3 have different string types for handling unicodes. Python 2 wants `basestring`, + # whereas Python 3 is OK with str. This causes issues with the XML parser, which returns + # unicode strings in Python 2. So, need to do this to make it work in both Python 2 and 3: + import sys + if sys.version_info >= (3, 0): + str_type = str + else: + str_type = basestring # noqa + + self.allowed_methods = [allowed_methods] if isinstance(allowed_methods, str_type) else allowed_methods + self.allowed_origins = [allowed_origins] if isinstance(allowed_origins, str_type) else allowed_origins + self.allowed_headers = [allowed_headers] if isinstance(allowed_headers, str_type) else allowed_headers + self.exposed_headers = [expose_headers] if isinstance(expose_headers, str_type) else expose_headers + self.max_age_seconds = max_age_seconds + + class FakeBucket(BaseModel): def __init__(self, name, region_name): @@ -307,6 +351,8 @@ class FakeBucket(BaseModel): self.policy = None self.website_configuration = None self.acl = get_canned_acl('private') + self.tags = FakeTagging() + self.cors = [] @property def location(self): @@ -336,6 +382,61 @@ class FakeBucket(BaseModel): def delete_lifecycle(self): self.rules = [] + def set_cors(self, rules): + from moto.s3.exceptions import InvalidRequest, MalformedXML + self.cors = [] + + if len(rules) > 100: + raise MalformedXML() + + # Python 2 and 3 have different string types for handling unicodes. Python 2 wants `basestring`, + # whereas Python 3 is OK with str. This causes issues with the XML parser, which returns + # unicode strings in Python 2. So, need to do this to make it work in both Python 2 and 3: + import sys + if sys.version_info >= (3, 0): + str_type = str + else: + str_type = basestring # noqa + + for rule in rules: + assert isinstance(rule["AllowedMethod"], list) or isinstance(rule["AllowedMethod"], str_type) + assert isinstance(rule["AllowedOrigin"], list) or isinstance(rule["AllowedOrigin"], str_type) + assert isinstance(rule.get("AllowedHeader", []), list) or isinstance(rule.get("AllowedHeader", ""), + str_type) + assert isinstance(rule.get("ExposedHeader", []), list) or isinstance(rule.get("ExposedHeader", ""), + str_type) + assert isinstance(rule.get("MaxAgeSeconds", "0"), str_type) + + if isinstance(rule["AllowedMethod"], str_type): + methods = [rule["AllowedMethod"]] + else: + methods = rule["AllowedMethod"] + + for method in methods: + if method not in ["GET", "PUT", "HEAD", "POST", "DELETE"]: + raise InvalidRequest(method) + + self.cors.append(CorsRule( + rule["AllowedMethod"], + rule["AllowedOrigin"], + rule.get("AllowedHeader"), + rule.get("ExposedHeader"), + rule.get("MaxAgeSecond") + )) + + def delete_cors(self): + self.cors = [] + + def set_tags(self, tagging): + self.tags = tagging + + def delete_tags(self): + self.tags = FakeTagging() + + @property + def tagging(self): + return self.tags + def set_website_configuration(self, website_configuration): self.website_configuration = website_configuration @@ -422,14 +523,15 @@ class S3Backend(BaseBackend): encoding_type=None, key_marker=None, max_keys=None, - version_id_marker=None): + version_id_marker=None, + prefix=''): bucket = self.get_bucket(bucket_name) if any((delimiter, encoding_type, key_marker, version_id_marker)): raise NotImplementedError( "Called get_bucket_versions with some of delimiter, encoding_type, key_marker, version_id_marker") - return itertools.chain(*(l for _, l in bucket.keys.iterlists())) + return itertools.chain(*(l for key, l in bucket.keys.iterlists() if key.startswith(prefix))) def get_bucket_policy(self, bucket_name): return self.get_bucket(bucket_name).policy @@ -509,6 +611,22 @@ class S3Backend(BaseBackend): key.set_tagging(tagging) return key + def put_bucket_tagging(self, bucket_name, tagging): + bucket = self.get_bucket(bucket_name) + bucket.set_tags(tagging) + + def delete_bucket_tagging(self, bucket_name): + bucket = self.get_bucket(bucket_name) + bucket.delete_tags() + + def put_bucket_cors(self, bucket_name, cors_rules): + bucket = self.get_bucket(bucket_name) + bucket.set_cors(cors_rules) + + def delete_bucket_cors(self, bucket_name): + bucket = self.get_bucket(bucket_name) + bucket.delete_cors() + def initiate_multipart(self, bucket_name, key_name, metadata): bucket = self.get_bucket(bucket_name) new_multipart = FakeMultipart(key_name, metadata) diff --git a/moto/s3/responses.py b/moto/s3/responses.py old mode 100644 new mode 100755 index ec1361cb8..b04cb9496 --- a/moto/s3/responses.py +++ b/moto/s3/responses.py @@ -188,7 +188,8 @@ class ResponseObject(_TemplateEnvironmentMixin): elif 'lifecycle' in querystring: bucket = self.backend.get_bucket(bucket_name) if not bucket.rules: - return 404, {}, "NoSuchLifecycleConfiguration" + template = self.response_template(S3_NO_LIFECYCLE) + return 404, {}, template.render(bucket_name=bucket_name) template = self.response_template( S3_BUCKET_LIFECYCLE_CONFIGURATION) return template.render(rules=bucket.rules) @@ -205,17 +206,35 @@ class ResponseObject(_TemplateEnvironmentMixin): elif 'website' in querystring: website_configuration = self.backend.get_bucket_website_configuration( bucket_name) + if not website_configuration: + template = self.response_template(S3_NO_BUCKET_WEBSITE_CONFIG) + return 404, {}, template.render(bucket_name=bucket_name) return website_configuration elif 'acl' in querystring: bucket = self.backend.get_bucket(bucket_name) template = self.response_template(S3_OBJECT_ACL_RESPONSE) return template.render(obj=bucket) + elif 'tagging' in querystring: + bucket = self.backend.get_bucket(bucket_name) + # "Special Error" if no tags: + if len(bucket.tagging.tag_set.tags) == 0: + template = self.response_template(S3_NO_BUCKET_TAGGING) + return 404, {}, template.render(bucket_name=bucket_name) + template = self.response_template(S3_BUCKET_TAGGING_RESPONSE) + return template.render(bucket=bucket) + elif "cors" in querystring: + bucket = self.backend.get_bucket(bucket_name) + if len(bucket.cors) == 0: + template = self.response_template(S3_NO_CORS_CONFIG) + return 404, {}, template.render(bucket_name=bucket_name) + template = self.response_template(S3_BUCKET_CORS_RESPONSE) + return template.render(bucket=bucket) elif 'versions' in querystring: delimiter = querystring.get('delimiter', [None])[0] encoding_type = querystring.get('encoding-type', [None])[0] key_marker = querystring.get('key-marker', [None])[0] max_keys = querystring.get('max-keys', [None])[0] - prefix = querystring.get('prefix', [None])[0] + prefix = querystring.get('prefix', [''])[0] version_id_marker = querystring.get('version-id-marker', [None])[0] bucket = self.backend.get_bucket(bucket_name) @@ -225,7 +244,8 @@ class ResponseObject(_TemplateEnvironmentMixin): encoding_type=encoding_type, key_marker=key_marker, max_keys=max_keys, - version_id_marker=version_id_marker + version_id_marker=version_id_marker, + prefix=prefix ) latest_versions = self.backend.get_bucket_latest_versions( bucket_name=bucket_name @@ -256,15 +276,25 @@ class ResponseObject(_TemplateEnvironmentMixin): if prefix and isinstance(prefix, six.binary_type): prefix = prefix.decode("utf-8") delimiter = querystring.get('delimiter', [None])[0] + max_keys = int(querystring.get('max-keys', [1000])[0]) + marker = querystring.get('marker', [None])[0] result_keys, result_folders = self.backend.prefix_query( bucket, prefix, delimiter) + + if marker: + result_keys = self._get_results_from_token(result_keys, marker) + + result_keys, is_truncated, _ = self._truncate_result(result_keys, max_keys) + template = self.response_template(S3_BUCKET_GET_RESPONSE) return 200, {}, template.render( bucket=bucket, prefix=prefix, delimiter=delimiter, result_keys=result_keys, - result_folders=result_folders + result_folders=result_folders, + is_truncated=is_truncated, + max_keys=max_keys ) def _handle_list_objects_v2(self, bucket_name, querystring): @@ -285,20 +315,10 @@ class ResponseObject(_TemplateEnvironmentMixin): if continuation_token or start_after: limit = continuation_token or start_after - continuation_index = 0 - for key in result_keys: - if key.name > limit: - break - continuation_index += 1 - result_keys = result_keys[continuation_index:] + result_keys = self._get_results_from_token(result_keys, limit) - if len(result_keys) > max_keys: - is_truncated = 'true' - result_keys = result_keys[:max_keys] - next_continuation_token = result_keys[-1].name - else: - is_truncated = 'false' - next_continuation_token = None + result_keys, is_truncated, \ + next_continuation_token = self._truncate_result(result_keys, max_keys) return template.render( bucket=bucket, @@ -313,6 +333,24 @@ class ResponseObject(_TemplateEnvironmentMixin): start_after=None if continuation_token else start_after ) + def _get_results_from_token(self, result_keys, token): + continuation_index = 0 + for key in result_keys: + if key.name > token: + break + continuation_index += 1 + return result_keys[continuation_index:] + + def _truncate_result(self, result_keys, max_keys): + if len(result_keys) > max_keys: + is_truncated = 'true' + result_keys = result_keys[:max_keys] + next_continuation_token = result_keys[-1].name + else: + is_truncated = 'false' + next_continuation_token = None + return result_keys, is_truncated, next_continuation_token + def _bucket_response_put(self, request, body, region_name, bucket_name, querystring, headers): if not request.headers.get('Content-Length'): return 411, {}, "Content-Length required" @@ -335,13 +373,23 @@ class ResponseObject(_TemplateEnvironmentMixin): self.backend.set_bucket_policy(bucket_name, body) return 'True' elif 'acl' in querystring: - acl = self._acl_from_headers(request.headers) # TODO: Support the XML-based ACL format - self.backend.set_bucket_acl(bucket_name, acl) + self.backend.set_bucket_acl(bucket_name, self._acl_from_headers(request.headers)) + return "" + elif "tagging" in querystring: + tagging = self._bucket_tagging_from_xml(body) + self.backend.put_bucket_tagging(bucket_name, tagging) return "" elif 'website' in querystring: self.backend.set_bucket_website_configuration(bucket_name, body) return "" + elif "cors" in querystring: + from moto.s3.exceptions import MalformedXML + try: + self.backend.put_bucket_cors(bucket_name, self._cors_from_xml(body)) + return "" + except KeyError: + raise MalformedXML() else: if body: try: @@ -358,6 +406,11 @@ class ResponseObject(_TemplateEnvironmentMixin): new_bucket = self.backend.get_bucket(bucket_name) else: raise + + if 'x-amz-acl' in request.headers: + # TODO: Support the XML-based ACL format + self.backend.set_bucket_acl(bucket_name, self._acl_from_headers(request.headers)) + template = self.response_template(S3_BUCKET_CREATE_RESPONSE) return 200, {}, template.render(bucket=new_bucket) @@ -365,6 +418,12 @@ class ResponseObject(_TemplateEnvironmentMixin): if 'policy' in querystring: self.backend.delete_bucket_policy(bucket_name, body) return 204, {}, "" + elif "tagging" in querystring: + self.backend.delete_bucket_tagging(bucket_name) + return 204, {}, "" + elif "cors" in querystring: + self.backend.delete_bucket_cors(bucket_name) + return 204, {}, "" elif 'lifecycle' in querystring: bucket = self.backend.get_bucket(bucket_name) bucket.delete_lifecycle() @@ -481,6 +540,23 @@ class ResponseObject(_TemplateEnvironmentMixin): key_name = self.parse_key_name(request, parsed_url.path) bucket_name = self.parse_bucket_name_from_url(request, full_url) + # Because we patch the requests library the boto/boto3 API + # requests go through this method but so do + # `requests.get("https://bucket-name.s3.amazonaws.com/file-name")` + # Here we deny public access to private files by checking the + # ACL and checking for the mere presence of an Authorization + # header. + if 'Authorization' not in request.headers: + if hasattr(request, 'url'): + signed_url = 'Signature=' in request.url + elif hasattr(request, 'requestline'): + signed_url = 'Signature=' in request.path + key = self.backend.get_key(bucket_name, key_name) + + if key: + if not key.acl.public_read and not signed_url: + return 403, {}, "" + if hasattr(request, 'body'): # Boto body = request.body @@ -566,6 +642,8 @@ class ResponseObject(_TemplateEnvironmentMixin): storage_class = request.headers.get('x-amz-storage-class', 'STANDARD') acl = self._acl_from_headers(request.headers) + if acl is None: + acl = self.backend.get_bucket(bucket_name).acl tagging = self._tagging_from_headers(request.headers) if 'acl' in query: @@ -696,6 +774,32 @@ class ResponseObject(_TemplateEnvironmentMixin): tagging = FakeTagging(tag_set) return tagging + def _bucket_tagging_from_xml(self, xml): + parsed_xml = xmltodict.parse(xml) + + tags = [] + # Optional if no tags are being sent: + if parsed_xml['Tagging'].get('TagSet'): + # If there is only 1 tag, then it's not a list: + if not isinstance(parsed_xml['Tagging']['TagSet']['Tag'], list): + tags.append(FakeTag(parsed_xml['Tagging']['TagSet']['Tag']['Key'], + parsed_xml['Tagging']['TagSet']['Tag']['Value'])) + else: + for tag in parsed_xml['Tagging']['TagSet']['Tag']: + tags.append(FakeTag(tag['Key'], tag['Value'])) + + tag_set = FakeTagSet(tags) + tagging = FakeTagging(tag_set) + return tagging + + def _cors_from_xml(self, xml): + parsed_xml = xmltodict.parse(xml) + + if isinstance(parsed_xml["CORSConfiguration"]["CORSRule"], list): + return [cors for cors in parsed_xml["CORSConfiguration"]["CORSRule"]] + + return [parsed_xml["CORSConfiguration"]["CORSRule"]] + def _key_response_delete(self, bucket_name, query, key_name, headers): if query.get('uploadId'): upload_id = query['uploadId'][0] @@ -775,9 +879,9 @@ S3_BUCKET_GET_RESPONSE = """ {{ bucket.name }} {{ prefix }} - 1000 + {{ max_keys }} {{ delimiter }} - false + {{ is_truncated }} {% for key in result_keys %} {{ key.name }} @@ -1022,6 +1126,46 @@ S3_OBJECT_TAGGING_RESPONSE = """\ """ +S3_BUCKET_TAGGING_RESPONSE = """ + + + {% for tag in bucket.tagging.tag_set.tags %} + + {{ tag.key }} + {{ tag.value }} + + {% endfor %} + +""" + +S3_BUCKET_CORS_RESPONSE = """ + + {% for cors in bucket.cors %} + + {% for origin in cors.allowed_origins %} + {{ origin }} + {% endfor %} + {% for method in cors.allowed_methods %} + {{ method }} + {% endfor %} + {% if cors.allowed_headers is not none %} + {% for header in cors.allowed_headers %} + {{ header }} + {% endfor %} + {% endif %} + {% if cors.exposed_headers is not none %} + {% for header in cors.exposed_headers %} + {{ header }} + {% endfor %} + {% endif %} + {% if cors.max_age_seconds is not none %} + {{ cors.max_age_seconds }} + {% endif %} + + {% endfor %} + +""" + S3_OBJECT_COPY_RESPONSE = """\ {{ key.etag }} @@ -1114,3 +1258,53 @@ S3_NO_POLICY = """ 9Gjjt1m+cjU4OPvX9O9/8RuvnG41MRb/18Oux2o5H5MY7ISNTlXN+Dz9IG62/ILVxhAGI0qyPfg= """ + +S3_NO_LIFECYCLE = """ + + NoSuchLifecycleConfiguration + The lifecycle configuration does not exist + {{ bucket_name }} + 44425877V1D0A2F9 + 9Gjjt1m+cjU4OPvX9O9/8RuvnG41MRb/18Oux2o5H5MY7ISNTlXN+Dz9IG62/ILVxhAGI0qyPfg= + +""" + +S3_NO_BUCKET_TAGGING = """ + + NoSuchTagSet + The TagSet does not exist + {{ bucket_name }} + 44425877V1D0A2F9 + 9Gjjt1m+cjU4OPvX9O9/8RuvnG41MRb/18Oux2o5H5MY7ISNTlXN+Dz9IG62/ILVxhAGI0qyPfg= + +""" + +S3_NO_BUCKET_WEBSITE_CONFIG = """ + + NoSuchWebsiteConfiguration + The specified bucket does not have a website configuration + {{ bucket_name }} + 44425877V1D0A2F9 + 9Gjjt1m+cjU4OPvX9O9/8RuvnG41MRb/18Oux2o5H5MY7ISNTlXN+Dz9IG62/ILVxhAGI0qyPfg= + +""" + +S3_INVALID_CORS_REQUEST = """ + + NoSuchWebsiteConfiguration + The specified bucket does not have a website configuration + {{ bucket_name }} + 44425877V1D0A2F9 + 9Gjjt1m+cjU4OPvX9O9/8RuvnG41MRb/18Oux2o5H5MY7ISNTlXN+Dz9IG62/ILVxhAGI0qyPfg= + +""" + +S3_NO_CORS_CONFIG = """ + + NoSuchCORSConfiguration + The CORS configuration does not exist + {{ bucket_name }} + 44425877V1D0A2F9 + 9Gjjt1m+cjU4OPvX9O9/8RuvnG41MRb/18Oux2o5H5MY7ISNTlXN+Dz9IG62/ILVxhAGI0qyPfg= + +""" diff --git a/moto/s3/urls.py b/moto/s3/urls.py index 8faad6282..1d439a549 100644 --- a/moto/s3/urls.py +++ b/moto/s3/urls.py @@ -4,7 +4,7 @@ from .responses import S3ResponseInstance url_bases = [ "https?://s3(.*).amazonaws.com", - "https?://(?P[a-zA-Z0-9\-_.]*)\.?s3(.*).amazonaws.com" + r"https?://(?P[a-zA-Z0-9\-_.]*)\.?s3(.*).amazonaws.com" ] diff --git a/moto/server.py b/moto/server.py index 8d0103cc2..e9f4c0904 100644 --- a/moto/server.py +++ b/moto/server.py @@ -1,22 +1,23 @@ from __future__ import unicode_literals + +import argparse import json import re import sys -import argparse -import six - -from six.moves.urllib.parse import urlencode - from threading import Lock +import six from flask import Flask from flask.testing import FlaskClient + +from six.moves.urllib.parse import urlencode from werkzeug.routing import BaseConverter from werkzeug.serving import run_simple from moto.backends import BACKENDS from moto.core.utils import convert_flask_to_httpretty_response + HTTP_METHODS = ["GET", "POST", "PUT", "DELETE", "HEAD", "PATCH"] @@ -61,7 +62,7 @@ class DomainDispatcherApplication(object): host = "instance_metadata" else: host = environ['HTTP_HOST'].split(':')[0] - if host == "localhost": + if host in {'localhost', 'motoserver'} or host.startswith("192.168."): # Fall back to parsing auth header to find service # ['Credential=sdffdsa', '20170220', 'us-east-1', 'sns', 'aws4_request'] try: @@ -139,10 +140,13 @@ def create_backend_app(service): else: endpoint = None - if endpoint in backend_app.view_functions: + original_endpoint = endpoint + index = 2 + while endpoint in backend_app.view_functions: # HACK: Sometimes we map the same view to multiple url_paths. Flask # requries us to have different names. - endpoint += "2" + endpoint = original_endpoint + str(index) + index += 1 backend_app.add_url_rule( url_path, diff --git a/moto/sns/exceptions.py b/moto/sns/exceptions.py index 092bb9d69..95b91acca 100644 --- a/moto/sns/exceptions.py +++ b/moto/sns/exceptions.py @@ -24,3 +24,11 @@ class SnsEndpointDisabled(RESTError): def __init__(self, message): super(SnsEndpointDisabled, self).__init__( "EndpointDisabled", message) + + +class SNSInvalidParameter(RESTError): + code = 400 + + def __init__(self, message): + super(SNSInvalidParameter, self).__init__( + "InvalidParameter", message) diff --git a/moto/sns/models.py b/moto/sns/models.py index 41b858891..5b7277d22 100644 --- a/moto/sns/models.py +++ b/moto/sns/models.py @@ -12,8 +12,10 @@ from moto.compat import OrderedDict from moto.core import BaseBackend, BaseModel from moto.core.utils import iso_8601_datetime_with_milliseconds from moto.sqs import sqs_backends +from moto.awslambda import lambda_backends + from .exceptions import ( - SNSNotFoundError, DuplicateSnsEndpointError, SnsEndpointDisabled + SNSNotFoundError, DuplicateSnsEndpointError, SnsEndpointDisabled, SNSInvalidParameter ) from .utils import make_arn_for_topic, make_arn_for_subscription @@ -76,15 +78,23 @@ class Subscription(BaseModel): self.endpoint = endpoint self.protocol = protocol self.arn = make_arn_for_subscription(self.topic.arn) + self.attributes = {} + self.confirmed = False def publish(self, message, message_id): if self.protocol == 'sqs': queue_name = self.endpoint.split(":")[-1] region = self.endpoint.split(":")[3] - sqs_backends[region].send_message(queue_name, message) + enveloped_message = json.dumps(self.get_post_data(message, message_id), sort_keys=True, indent=2, separators=(',', ': ')) + sqs_backends[region].send_message(queue_name, enveloped_message) elif self.protocol in ['http', 'https']: post_data = self.get_post_data(message, message_id) requests.post(self.endpoint, json=post_data) + elif self.protocol == 'lambda': + # TODO: support bad function name + function_name = self.endpoint.split(":")[-1] + region = self.arn.split(':')[3] + lambda_backends[region].send_message(function_name, message) def get_post_data(self, message, message_id): return { @@ -170,12 +180,18 @@ class SNSBackend(BaseBackend): self.applications = {} self.platform_endpoints = {} self.region_name = region_name + self.sms_attributes = {} + self.opt_out_numbers = ['+447420500600', '+447420505401', '+447632960543', '+447632960028', '+447700900149', '+447700900550', '+447700900545', '+447700900907'] + self.permissions = {} def reset(self): region_name = self.region_name self.__dict__ = {} self.__init__(region_name) + def update_sms_attributes(self, attrs): + self.sms_attributes.update(attrs) + def create_topic(self, name): topic = Topic(name, self) self.topics[topic.arn] = topic @@ -212,6 +228,12 @@ class SNSBackend(BaseBackend): except KeyError: raise SNSNotFoundError("Topic with arn {0} not found".format(arn)) + def get_topic_from_phone_number(self, number): + for subscription in self.subscriptions.values(): + if subscription.protocol == 'sms' and subscription.endpoint == number: + return subscription.topic.arn + raise SNSNotFoundError('Could not find valid subscription') + def set_topic_attribute(self, topic_arn, attribute_name, attribute_value): topic = self.get_topic(topic_arn) setattr(topic, attribute_name, attribute_value) @@ -300,6 +322,26 @@ class SNSBackend(BaseBackend): raise SNSNotFoundError( "Endpoint with arn {0} not found".format(arn)) + def get_subscription_attributes(self, arn): + _subscription = [_ for _ in self.subscriptions.values() if _.arn == arn] + if not _subscription: + raise SNSNotFoundError("Subscription with arn {0} not found".format(arn)) + subscription = _subscription[0] + + return subscription.attributes + + def set_subscription_attributes(self, arn, name, value): + if name not in ['RawMessageDelivery', 'DeliveryPolicy']: + raise SNSInvalidParameter('AttributeName') + + # TODO: should do validation + _subscription = [_ for _ in self.subscriptions.values() if _.arn == arn] + if not _subscription: + raise SNSNotFoundError("Subscription with arn {0} not found".format(arn)) + subscription = _subscription[0] + + subscription.attributes[name] = value + sns_backends = {} for region in boto.sns.regions(): diff --git a/moto/sns/responses.py b/moto/sns/responses.py index edb82e40c..85764aa58 100644 --- a/moto/sns/responses.py +++ b/moto/sns/responses.py @@ -1,17 +1,27 @@ from __future__ import unicode_literals import json +import re +from collections import defaultdict from moto.core.responses import BaseResponse from moto.core.utils import camelcase_to_underscores from .models import sns_backends +from .exceptions import SNSNotFoundError +from .utils import is_e164 class SNSResponse(BaseResponse): + SMS_ATTR_REGEX = re.compile(r'^attributes\.entry\.(?P\d+)\.(?Pkey|value)$') + OPT_OUT_PHONE_NUMBER_REGEX = re.compile(r'^\+?\d+$') @property def backend(self): return sns_backends[self.region] + def _error(self, code, message, sender='Sender'): + template = self.response_template(ERROR_RESPONSE) + return template.render(code=code, message=message, sender=sender) + def _get_attributes(self): attributes = self._get_list_prefix('Attributes.entry') return dict( @@ -128,6 +138,13 @@ class SNSResponse(BaseResponse): topic_arn = self._get_param('TopicArn') endpoint = self._get_param('Endpoint') protocol = self._get_param('Protocol') + + if protocol == 'sms' and not is_e164(endpoint): + return self._error( + 'InvalidParameter', + 'Phone number does not meet the E164 format' + ), dict(status=400) + subscription = self.backend.subscribe(topic_arn, endpoint, protocol) if self.request_json: @@ -221,7 +238,28 @@ class SNSResponse(BaseResponse): def publish(self): target_arn = self._get_param('TargetArn') topic_arn = self._get_param('TopicArn') - arn = target_arn if target_arn else topic_arn + phone_number = self._get_param('PhoneNumber') + if phone_number is not None: + # Check phone is correct syntax (e164) + if not is_e164(phone_number): + return self._error( + 'InvalidParameter', + 'Phone number does not meet the E164 format' + ), dict(status=400) + + # Look up topic arn by phone number + try: + arn = self.backend.get_topic_from_phone_number(phone_number) + except SNSNotFoundError: + return self._error( + 'ParameterValueInvalid', + 'Could not find topic associated with phone number' + ), dict(status=400) + elif target_arn is not None: + arn = target_arn + else: + arn = topic_arn + message = self._get_param('Message') message_id = self.backend.publish(arn, message) @@ -445,6 +483,145 @@ class SNSResponse(BaseResponse): template = self.response_template(DELETE_ENDPOINT_TEMPLATE) return template.render() + def get_subscription_attributes(self): + arn = self._get_param('SubscriptionArn') + attributes = self.backend.get_subscription_attributes(arn) + template = self.response_template(GET_SUBSCRIPTION_ATTRIBUTES_TEMPLATE) + return template.render(attributes=attributes) + + def set_subscription_attributes(self): + arn = self._get_param('SubscriptionArn') + attr_name = self._get_param('AttributeName') + attr_value = self._get_param('AttributeValue') + self.backend.set_subscription_attributes(arn, attr_name, attr_value) + template = self.response_template(SET_SUBSCRIPTION_ATTRIBUTES_TEMPLATE) + return template.render() + + def set_sms_attributes(self): + # attributes.entry.1.key + # attributes.entry.1.value + # to + # 1: {key:X, value:Y} + temp_dict = defaultdict(dict) + for key, value in self.querystring.items(): + match = self.SMS_ATTR_REGEX.match(key) + if match is not None: + temp_dict[match.group('index')][match.group('type')] = value[0] + + # 1: {key:X, value:Y} + # to + # X: Y + # All of this, just to take into account when people provide invalid stuff. + result = {} + for item in temp_dict.values(): + if 'key' in item and 'value' in item: + result[item['key']] = item['value'] + + self.backend.update_sms_attributes(result) + + template = self.response_template(SET_SMS_ATTRIBUTES_TEMPLATE) + return template.render() + + def get_sms_attributes(self): + filter_list = set() + for key, value in self.querystring.items(): + if key.startswith('attributes.member.1'): + filter_list.add(value[0]) + + if len(filter_list) > 0: + result = {k: v for k, v in self.backend.sms_attributes.items() if k in filter_list} + else: + result = self.backend.sms_attributes + + template = self.response_template(GET_SMS_ATTRIBUTES_TEMPLATE) + return template.render(attributes=result) + + def check_if_phone_number_is_opted_out(self): + number = self._get_param('phoneNumber') + if self.OPT_OUT_PHONE_NUMBER_REGEX.match(number) is None: + error_response = self._error( + code='InvalidParameter', + message='Invalid parameter: PhoneNumber Reason: input incorrectly formatted' + ) + return error_response, dict(status=400) + + # There should be a nicer way to set if a nubmer has opted out + template = self.response_template(CHECK_IF_OPTED_OUT_TEMPLATE) + return template.render(opt_out=str(number.endswith('99')).lower()) + + def list_phone_numbers_opted_out(self): + template = self.response_template(LIST_OPTOUT_TEMPLATE) + return template.render(opt_outs=self.backend.opt_out_numbers) + + def opt_in_phone_number(self): + number = self._get_param('phoneNumber') + + try: + self.backend.opt_out_numbers.remove(number) + except ValueError: + pass + + template = self.response_template(OPT_IN_NUMBER_TEMPLATE) + return template.render() + + def add_permission(self): + arn = self._get_param('TopicArn') + label = self._get_param('Label') + accounts = self._get_multi_param('AWSAccountId.member.') + action = self._get_multi_param('ActionName.member.') + + if arn not in self.backend.topics: + error_response = self._error('NotFound', 'Topic does not exist') + return error_response, dict(status=404) + + key = (arn, label) + self.backend.permissions[key] = {'accounts': accounts, 'action': action} + + template = self.response_template(ADD_PERMISSION_TEMPLATE) + return template.render() + + def remove_permission(self): + arn = self._get_param('TopicArn') + label = self._get_param('Label') + + if arn not in self.backend.topics: + error_response = self._error('NotFound', 'Topic does not exist') + return error_response, dict(status=404) + + try: + key = (arn, label) + del self.backend.permissions[key] + except KeyError: + pass + + template = self.response_template(DEL_PERMISSION_TEMPLATE) + return template.render() + + def confirm_subscription(self): + arn = self._get_param('TopicArn') + + if arn not in self.backend.topics: + error_response = self._error('NotFound', 'Topic does not exist') + return error_response, dict(status=404) + + # Once Tokens are stored by the `subscribe` endpoint and distributed + # to the client somehow, then we can check validity of tokens + # presented to this method. The following code works, all thats + # needed is to perform a token check and assign that value to the + # `already_subscribed` variable. + # + # token = self._get_param('Token') + # auth = self._get_param('AuthenticateOnUnsubscribe') + # if already_subscribed: + # error_response = self._error( + # code='AuthorizationError', + # message='Subscription already confirmed' + # ) + # return error_response, dict(status=400) + + template = self.response_template(CONFIRM_SUBSCRIPTION_TEMPLATE) + return template.render(sub_arn='{0}:68762e72-e9b1-410a-8b3b-903da69ee1d5'.format(arn)) + CREATE_TOPIC_TEMPLATE = """ @@ -719,3 +896,110 @@ LIST_SUBSCRIPTIONS_BY_TOPIC_TEMPLATE = """384ac68d-3775-11df-8963-01868b7c937a """ + + +# Not responding aws system attribetus like 'Owner' and 'SubscriptionArn' +GET_SUBSCRIPTION_ATTRIBUTES_TEMPLATE = """ + + + {% for name, value in attributes.items() %} + + {{ name }} + {{ value }} + + {% endfor %} + + + + 057f074c-33a7-11df-9540-99d0768312d3 + +""" + + +SET_SUBSCRIPTION_ATTRIBUTES_TEMPLATE = """ + + a8763b99-33a7-11df-a9b7-05d48da6f042 + +""" + +SET_SMS_ATTRIBUTES_TEMPLATE = """ + + + 26332069-c04a-5428-b829-72524b56a364 + +""" + +GET_SMS_ATTRIBUTES_TEMPLATE = """ + + + {% for name, value in attributes.items() %} + + {{ name }} + {{ value }} + + {% endfor %} + + + + 287f9554-8db3-5e66-8abc-c76f0186db7e + +""" + +CHECK_IF_OPTED_OUT_TEMPLATE = """ + + {{ opt_out }} + + + 287f9554-8db3-5e66-8abc-c76f0186db7e + +""" + +ERROR_RESPONSE = """ + + {{ sender }} + {{ code }} + {{ message }} + + 9dd01905-5012-5f99-8663-4b3ecd0dfaef +""" + +LIST_OPTOUT_TEMPLATE = """ + + + {% for item in opt_outs %} + {{ item }} + {% endfor %} + + + + 985e196d-a237-51b6-b33a-4b5601276b38 + +""" + +OPT_IN_NUMBER_TEMPLATE = """ + + + 4c61842c-0796-50ef-95ac-d610c0bc8cf8 + +""" + +ADD_PERMISSION_TEMPLATE = """ + + c046e713-c5ff-5888-a7bc-b52f0e4f1299 + +""" + +DEL_PERMISSION_TEMPLATE = """ + + e767cc9f-314b-5e1b-b283-9ea3fd4e38a3 + +""" + +CONFIRM_SUBSCRIPTION_TEMPLATE = """ + + {{ sub_arn }} + + + 16eb4dde-7b3c-5b3e-a22a-1fe2a92d3293 + +""" diff --git a/moto/sns/utils.py b/moto/sns/utils.py index 864c3af6b..7793b0f6d 100644 --- a/moto/sns/utils.py +++ b/moto/sns/utils.py @@ -1,6 +1,9 @@ from __future__ import unicode_literals +import re import uuid +E164_REGEX = re.compile(r'^\+?[1-9]\d{1,14}$') + def make_arn_for_topic(account_id, name, region_name): return "arn:aws:sns:{0}:{1}:{2}".format(region_name, account_id, name) @@ -9,3 +12,7 @@ def make_arn_for_topic(account_id, name, region_name): def make_arn_for_subscription(topic_arn): subscription_id = uuid.uuid4() return "{0}:{1}".format(topic_arn, subscription_id) + + +def is_e164(number): + return E164_REGEX.match(number) is not None diff --git a/moto/sqs/models.py b/moto/sqs/models.py index f6657269c..e9d889453 100644 --- a/moto/sqs/models.py +++ b/moto/sqs/models.py @@ -12,10 +12,7 @@ import boto.sqs from moto.core import BaseBackend, BaseModel from moto.core.utils import camelcase_to_underscores, get_random_message_id, unix_time, unix_time_millis from .utils import generate_receipt_handle -from .exceptions import ( - ReceiptHandleIsInvalid, - MessageNotInflight -) +from .exceptions import ReceiptHandleIsInvalid, MessageNotInflight, MessageAttributesInvalid DEFAULT_ACCOUNT_ID = 123456789012 DEFAULT_SENDER_ID = "AIDAIT2UOQQY3AUEKVGXU" @@ -59,6 +56,7 @@ class Message(BaseModel): return str.encode('utf-8') return str md5 = hashlib.md5() + struct_format = "!I".encode('ascii') # ensure it's a bytestring for name in sorted(self.message_attributes.keys()): attr = self.message_attributes[name] data_type = attr['data_type'] @@ -67,10 +65,10 @@ class Message(BaseModel): # Each part of each attribute is encoded right after it's # own length is packed into a 4-byte integer # 'timestamp' -> b'\x00\x00\x00\t' - encoded += struct.pack("!I", len(utf8(name))) + utf8(name) + encoded += struct.pack(struct_format, len(utf8(name))) + utf8(name) # The datatype is additionally given a final byte # representing which type it is - encoded += struct.pack("!I", len(data_type)) + utf8(data_type) + encoded += struct.pack(struct_format, len(data_type)) + utf8(data_type) encoded += TRANSPORT_TYPE_ENCODINGS[data_type] if data_type == 'String' or data_type == 'Number': @@ -86,7 +84,7 @@ class Message(BaseModel): # MD5 so as not to break client softwre return('deadbeefdeadbeefdeadbeefdeadbeef') - encoded += struct.pack("!I", len(utf8(value))) + utf8(value) + encoded += struct.pack(struct_format, len(utf8(value))) + utf8(value) md5.update(encoded) return md5.hexdigest() @@ -150,8 +148,12 @@ class Queue(BaseModel): camelcase_attributes = ['ApproximateNumberOfMessages', 'ApproximateNumberOfMessagesDelayed', 'ApproximateNumberOfMessagesNotVisible', + 'ContentBasedDeduplication', 'CreatedTimestamp', 'DelaySeconds', + 'FifoQueue', + 'KmsDataKeyReusePeriodSeconds', + 'KmsMasterKeyId', 'LastModifiedTimestamp', 'MaximumMessageSize', 'MessageRetentionPeriod', @@ -160,25 +162,35 @@ class Queue(BaseModel): 'VisibilityTimeout', 'WaitTimeSeconds'] - def __init__(self, name, visibility_timeout, wait_time_seconds, region): + def __init__(self, name, region, **kwargs): self.name = name - self.visibility_timeout = visibility_timeout or 30 + self.visibility_timeout = int(kwargs.get('VisibilityTimeout', 30)) self.region = region - # wait_time_seconds will be set to immediate return messages - self.wait_time_seconds = int(wait_time_seconds) if wait_time_seconds else 0 self._messages = [] now = unix_time() + # kwargs can also have: + # [Policy, RedrivePolicy] + self.fifo_queue = kwargs.get('FifoQueue', 'false') == 'true' + self.content_based_deduplication = kwargs.get('ContentBasedDeduplication', 'false') == 'true' + self.kms_master_key_id = kwargs.get('KmsMasterKeyId', 'alias/aws/sqs') + self.kms_data_key_reuse_period_seconds = int(kwargs.get('KmsDataKeyReusePeriodSeconds', 300)) self.created_timestamp = now - self.delay_seconds = 0 + self.delay_seconds = int(kwargs.get('DelaySeconds', 0)) self.last_modified_timestamp = now - self.maximum_message_size = 64 << 10 - self.message_retention_period = 86400 * 4 # four days - self.queue_arn = 'arn:aws:sqs:{0}:123456789012:{1}'.format( - self.region, self.name) - self.receive_message_wait_time_seconds = 0 + self.maximum_message_size = int(kwargs.get('MaximumMessageSize', 64 << 10)) + self.message_retention_period = int(kwargs.get('MessageRetentionPeriod', 86400 * 4)) # four days + self.queue_arn = 'arn:aws:sqs:{0}:123456789012:{1}'.format(self.region, self.name) + self.receive_message_wait_time_seconds = int(kwargs.get('ReceiveMessageWaitTimeSeconds', 0)) + + # wait_time_seconds will be set to immediate return messages + self.wait_time_seconds = int(kwargs.get('WaitTimeSeconds', 0)) + + # Check some conditions + if self.fifo_queue and not self.name.endswith('.fifo'): + raise MessageAttributesInvalid('Queue name must end in .fifo for FIFO queues') @classmethod def create_from_cloudformation_json(cls, resource_name, cloudformation_json, region_name): @@ -187,8 +199,8 @@ class Queue(BaseModel): sqs_backend = sqs_backends[region_name] return sqs_backend.create_queue( name=properties['QueueName'], - visibility_timeout=properties.get('VisibilityTimeout'), - wait_time_seconds=properties.get('WaitTimeSeconds') + region=region_name, + **properties ) @classmethod @@ -232,8 +244,10 @@ class Queue(BaseModel): def attributes(self): result = {} for attribute in self.camelcase_attributes: - result[attribute] = getattr( - self, camelcase_to_underscores(attribute)) + attr = getattr(self, camelcase_to_underscores(attribute)) + if isinstance(attr, bool): + attr = str(attr).lower() + result[attribute] = attr return result def url(self, request_url): @@ -267,11 +281,14 @@ class SQSBackend(BaseBackend): self.__dict__ = {} self.__init__(region_name) - def create_queue(self, name, visibility_timeout, wait_time_seconds): + def create_queue(self, name, **kwargs): queue = self.queues.get(name) if queue is None: - queue = Queue(name, visibility_timeout, - wait_time_seconds, self.region_name) + try: + kwargs.pop('region') + except KeyError: + pass + queue = Queue(name, region=self.region_name, **kwargs) self.queues[name] = queue return queue diff --git a/moto/sqs/responses.py b/moto/sqs/responses.py index ba4a56b8f..e0e493ad8 100644 --- a/moto/sqs/responses.py +++ b/moto/sqs/responses.py @@ -28,8 +28,7 @@ class SQSResponse(BaseResponse): @property def attribute(self): if not hasattr(self, '_attribute'): - self._attribute = dict([(a['name'], a['value']) - for a in self._get_list_prefix('Attribute')]) + self._attribute = self._get_map_prefix('Attribute', key_end='Name', value_end='Value') return self._attribute def _get_queue_name(self): @@ -58,17 +57,25 @@ class SQSResponse(BaseResponse): return 404, headers, ERROR_INEXISTENT_QUEUE return status_code, headers, body + def _error(self, code, message, status=400): + template = self.response_template(ERROR_TEMPLATE) + return template.render(code=code, message=message), dict(status=status) + def create_queue(self): request_url = urlparse(self.uri) - queue_name = self.querystring.get("QueueName")[0] - queue = self.sqs_backend.create_queue(queue_name, visibility_timeout=self.attribute.get('VisibilityTimeout'), - wait_time_seconds=self.attribute.get('WaitTimeSeconds')) + queue_name = self._get_param("QueueName") + + try: + queue = self.sqs_backend.create_queue(queue_name, **self.attribute) + except MessageAttributesInvalid as e: + return self._error('InvalidParameterValue', e.description) + template = self.response_template(CREATE_QUEUE_RESPONSE) return template.render(queue=queue, request_url=request_url) def get_queue_url(self): request_url = urlparse(self.uri) - queue_name = self.querystring.get("QueueName")[0] + queue_name = self._get_param("QueueName") queue = self.sqs_backend.get_queue(queue_name) if queue: template = self.response_template(GET_QUEUE_URL_RESPONSE) @@ -78,14 +85,14 @@ class SQSResponse(BaseResponse): def list_queues(self): request_url = urlparse(self.uri) - queue_name_prefix = self.querystring.get("QueueNamePrefix", [None])[0] + queue_name_prefix = self._get_param('QueueNamePrefix') queues = self.sqs_backend.list_queues(queue_name_prefix) template = self.response_template(LIST_QUEUES_RESPONSE) return template.render(queues=queues, request_url=request_url) def change_message_visibility(self): queue_name = self._get_queue_name() - receipt_handle = self.querystring.get("ReceiptHandle")[0] + receipt_handle = self._get_param('ReceiptHandle') try: visibility_timeout = self._get_validated_visibility_timeout() @@ -111,19 +118,15 @@ class SQSResponse(BaseResponse): return template.render(queue=queue) def set_queue_attributes(self): + # TODO validate self.get_param('QueueUrl') queue_name = self._get_queue_name() - if "Attribute.Name" in self.querystring: - key = camelcase_to_underscores( - self.querystring.get("Attribute.Name")[0]) - value = self.querystring.get("Attribute.Value")[0] - self.sqs_backend.set_queue_attribute(queue_name, key, value) - for a in self._get_list_prefix("Attribute"): - key = camelcase_to_underscores(a["name"]) - value = a["value"] + for key, value in self.attribute.items(): + key = camelcase_to_underscores(key) self.sqs_backend.set_queue_attribute(queue_name, key, value) return SET_QUEUE_ATTRIBUTE_RESPONSE def delete_queue(self): + # TODO validate self.get_param('QueueUrl') queue_name = self._get_queue_name() queue = self.sqs_backend.delete_queue(queue_name) if not queue: @@ -133,17 +136,12 @@ class SQSResponse(BaseResponse): return template.render(queue=queue) def send_message(self): - message = self.querystring.get("MessageBody")[0] - delay_seconds = self.querystring.get('DelaySeconds') + message = self._get_param('MessageBody') + delay_seconds = int(self._get_param('DelaySeconds', 0)) if len(message) > MAXIMUM_MESSAGE_LENGTH: return ERROR_TOO_LONG_RESPONSE, dict(status=400) - if delay_seconds: - delay_seconds = int(delay_seconds[0]) - else: - delay_seconds = 0 - try: message_attributes = parse_message_attributes(self.querystring) except MessageAttributesInvalid as e: @@ -470,3 +468,13 @@ ERROR_INEXISTENT_QUEUE = """ + + Sender + {{ code }} + {{ message }} + + + 6fde8d1e-52cd-4581-8cd9-c512f4c64223 +""" diff --git a/moto/ssm/models.py b/moto/ssm/models.py index 63cb3c8ba..a0e4a2155 100644 --- a/moto/ssm/models.py +++ b/moto/ssm/models.py @@ -52,6 +52,16 @@ class SimpleSystemManagerBackend(BaseBackend): except KeyError: pass + def delete_parameters(self, names): + result = [] + for name in names: + try: + del self._parameters[name] + result.append(name) + except KeyError: + pass + return result + def get_all_parameters(self): result = [] for k, _ in self._parameters.items(): @@ -65,6 +75,11 @@ class SimpleSystemManagerBackend(BaseBackend): result.append(self._parameters[name]) return result + def get_parameter(self, name, with_decryption): + if name in self._parameters: + return self._parameters[name] + return None + def put_parameter(self, name, description, value, type, keyid, overwrite): if not overwrite and name in self._parameters: return diff --git a/moto/ssm/responses.py b/moto/ssm/responses.py index 1fa1a81b2..3b75ada09 100644 --- a/moto/ssm/responses.py +++ b/moto/ssm/responses.py @@ -26,6 +26,40 @@ class SimpleSystemManagerResponse(BaseResponse): self.ssm_backend.delete_parameter(name) return json.dumps({}) + def delete_parameters(self): + names = self._get_param('Names') + result = self.ssm_backend.delete_parameters(names) + + response = { + 'DeletedParameters': [], + 'InvalidParameters': [] + } + + for name in names: + if name in result: + response['DeletedParameters'].append(name) + else: + response['InvalidParameters'].append(name) + return json.dumps(response) + + def get_parameter(self): + name = self._get_param('Name') + with_decryption = self._get_param('WithDecryption') + + result = self.ssm_backend.get_parameter(name, with_decryption) + + if result is None: + error = { + '__type': 'ParameterNotFound', + 'message': 'Parameter {0} not found.'.format(name) + } + return json.dumps(error), dict(status=400) + + response = { + 'Parameter': result.response_object(with_decryption) + } + return json.dumps(response) + def get_parameters(self): names = self._get_param('Names') with_decryption = self._get_param('WithDecryption') @@ -41,6 +75,10 @@ class SimpleSystemManagerResponse(BaseResponse): param_data = parameter.response_object(with_decryption) response['Parameters'].append(param_data) + param_names = [param.name for param in result] + for name in names: + if name not in param_names: + response['InvalidParameters'].append(name) return json.dumps(response) def describe_parameters(self): diff --git a/moto/xray/__init__.py b/moto/xray/__init__.py new file mode 100644 index 000000000..7b32ca0b0 --- /dev/null +++ b/moto/xray/__init__.py @@ -0,0 +1,6 @@ +from __future__ import unicode_literals +from .models import xray_backends +from ..core.models import base_decorator + +xray_backend = xray_backends['us-east-1'] +mock_xray = base_decorator(xray_backends) diff --git a/moto/xray/exceptions.py b/moto/xray/exceptions.py new file mode 100644 index 000000000..24f700178 --- /dev/null +++ b/moto/xray/exceptions.py @@ -0,0 +1,39 @@ +import json + + +class AWSError(Exception): + CODE = None + STATUS = 400 + + def __init__(self, message, code=None, status=None): + self.message = message + self.code = code if code is not None else self.CODE + self.status = status if status is not None else self.STATUS + + def response(self): + return json.dumps({'__type': self.code, 'message': self.message}), dict(status=self.status) + + +class InvalidRequestException(AWSError): + CODE = 'InvalidRequestException' + + +class BadSegmentException(Exception): + def __init__(self, seg_id=None, code=None, message=None): + self.id = seg_id + self.code = code + self.message = message + + def __repr__(self): + return ''.format('-'.join([self.id, self.code, self.message])) + + def to_dict(self): + result = {} + if self.id is not None: + result['Id'] = self.id + if self.code is not None: + result['ErrorCode'] = self.code + if self.message is not None: + result['Message'] = self.message + + return result diff --git a/moto/xray/models.py b/moto/xray/models.py new file mode 100644 index 000000000..b2d418232 --- /dev/null +++ b/moto/xray/models.py @@ -0,0 +1,251 @@ +from __future__ import unicode_literals + +import bisect +import datetime +from collections import defaultdict +import json +from moto.core import BaseBackend, BaseModel +from moto.ec2 import ec2_backends +from .exceptions import BadSegmentException, AWSError + + +class TelemetryRecords(BaseModel): + def __init__(self, instance_id, hostname, resource_arn, records): + self.instance_id = instance_id + self.hostname = hostname + self.resource_arn = resource_arn + self.records = records + + @classmethod + def from_json(cls, json): + instance_id = json.get('EC2InstanceId', None) + hostname = json.get('Hostname') + resource_arn = json.get('ResourceARN') + telemetry_records = json['TelemetryRecords'] + + return cls(instance_id, hostname, resource_arn, telemetry_records) + + +# https://docs.aws.amazon.com/xray/latest/devguide/xray-api-segmentdocuments.html +class TraceSegment(BaseModel): + def __init__(self, name, segment_id, trace_id, start_time, raw, end_time=None, in_progress=False, service=None, user=None, + origin=None, parent_id=None, http=None, aws=None, metadata=None, annotations=None, subsegments=None, **kwargs): + self.name = name + self.id = segment_id + self.trace_id = trace_id + self._trace_version = None + self._original_request_start_time = None + self._trace_identifier = None + self.start_time = start_time + self._start_date = None + self.end_time = end_time + self._end_date = None + self.in_progress = in_progress + self.service = service + self.user = user + self.origin = origin + self.parent_id = parent_id + self.http = http + self.aws = aws + self.metadata = metadata + self.annotations = annotations + self.subsegments = subsegments + self.misc = kwargs + + # Raw json string + self.raw = raw + + def __lt__(self, other): + return self.start_date < other.start_date + + @property + def trace_version(self): + if self._trace_version is None: + self._trace_version = int(self.trace_id.split('-', 1)[0]) + return self._trace_version + + @property + def request_start_date(self): + if self._original_request_start_time is None: + start_time = int(self.trace_id.split('-')[1], 16) + self._original_request_start_time = datetime.datetime.fromtimestamp(start_time) + return self._original_request_start_time + + @property + def start_date(self): + if self._start_date is None: + self._start_date = datetime.datetime.fromtimestamp(self.start_time) + return self._start_date + + @property + def end_date(self): + if self._end_date is None: + self._end_date = datetime.datetime.fromtimestamp(self.end_time) + return self._end_date + + @classmethod + def from_dict(cls, data, raw): + # Check manditory args + if 'id' not in data: + raise BadSegmentException(code='MissingParam', message='Missing segment ID') + seg_id = data['id'] + data['segment_id'] = seg_id # Just adding this key for future convenience + + for arg in ('name', 'trace_id', 'start_time'): + if arg not in data: + raise BadSegmentException(seg_id=seg_id, code='MissingParam', message='Missing segment ID') + + if 'end_time' not in data and 'in_progress' not in data: + raise BadSegmentException(seg_id=seg_id, code='MissingParam', message='Missing end_time or in_progress') + if 'end_time' not in data and data['in_progress'] == 'false': + raise BadSegmentException(seg_id=seg_id, code='MissingParam', message='Missing end_time') + + return cls(raw=raw, **data) + + +class SegmentCollection(object): + def __init__(self): + self._traces = defaultdict(self._new_trace_item) + + @staticmethod + def _new_trace_item(): + return { + 'start_date': datetime.datetime(1970, 1, 1), + 'end_date': datetime.datetime(1970, 1, 1), + 'finished': False, + 'trace_id': None, + 'segments': [] + } + + def put_segment(self, segment): + # insert into a sorted list + bisect.insort_left(self._traces[segment.trace_id]['segments'], segment) + + # Get the last segment (takes into account incorrect ordering) + # and if its the last one, mark trace as complete + if self._traces[segment.trace_id]['segments'][-1].end_time is not None: + self._traces[segment.trace_id]['finished'] = True + + start_time = self._traces[segment.trace_id]['segments'][0].start_date + end_time = self._traces[segment.trace_id]['segments'][-1].end_date + self._traces[segment.trace_id]['start_date'] = start_time + self._traces[segment.trace_id]['end_date'] = end_time + self._traces[segment.trace_id]['trace_id'] = segment.trace_id + # Todo consolidate trace segments into a trace. + # not enough working knowledge of xray to do this + + def summary(self, start_time, end_time, filter_expression=None, sampling=False): + # This beast https://docs.aws.amazon.com/xray/latest/api/API_GetTraceSummaries.html#API_GetTraceSummaries_ResponseSyntax + if filter_expression is not None: + raise AWSError('Not implemented yet - moto', code='InternalFailure', status=500) + + summaries = [] + + for tid, trace in self._traces.items(): + if trace['finished'] and start_time < trace['start_date'] and trace['end_date'] < end_time: + duration = int((trace['end_date'] - trace['start_date']).total_seconds()) + # this stuff is mostly guesses, refer to TODO above + has_error = any(['error' in seg.misc for seg in trace['segments']]) + has_fault = any(['fault' in seg.misc for seg in trace['segments']]) + has_throttle = any(['throttle' in seg.misc for seg in trace['segments']]) + + # Apparently all of these options are optional + summary_part = { + 'Annotations': {}, # Not implemented yet + 'Duration': duration, + 'HasError': has_error, + 'HasFault': has_fault, + 'HasThrottle': has_throttle, + 'Http': {}, # Not implemented yet + 'Id': tid, + 'IsParital': False, # needs lots more work to work on partials + 'ResponseTime': 1, # definitely 1ms resposnetime + 'ServiceIds': [], # Not implemented yet + 'Users': {} # Not implemented yet + } + summaries.append(summary_part) + + result = { + "ApproximateTime": int((datetime.datetime.now() - datetime.datetime(1970, 1, 1)).total_seconds()), + "TracesProcessedCount": len(summaries), + "TraceSummaries": summaries + } + + return result + + def get_trace_ids(self, trace_ids): + traces = [] + unprocessed = [] + + # Its a default dict + existing_trace_ids = list(self._traces.keys()) + for trace_id in trace_ids: + if trace_id in existing_trace_ids: + traces.append(self._traces[trace_id]) + else: + unprocessed.append(trace_id) + + return traces, unprocessed + + +class XRayBackend(BaseBackend): + + def __init__(self): + self._telemetry_records = [] + self._segment_collection = SegmentCollection() + + def add_telemetry_records(self, json): + self._telemetry_records.append( + TelemetryRecords.from_json(json) + ) + + def process_segment(self, doc): + try: + data = json.loads(doc) + except ValueError: + raise BadSegmentException(code='JSONFormatError', message='Bad JSON data') + + try: + # Get Segment Object + segment = TraceSegment.from_dict(data, raw=doc) + except ValueError: + raise BadSegmentException(code='JSONFormatError', message='Bad JSON data') + + try: + # Store Segment Object + self._segment_collection.put_segment(segment) + except Exception as err: + raise BadSegmentException(seg_id=segment.id, code='InternalFailure', message=str(err)) + + def get_trace_summary(self, start_time, end_time, filter_expression, summaries): + return self._segment_collection.summary(start_time, end_time, filter_expression, summaries) + + def get_trace_ids(self, trace_ids, next_token): + traces, unprocessed_ids = self._segment_collection.get_trace_ids(trace_ids) + + result = { + 'Traces': [], + 'UnprocessedTraceIds': unprocessed_ids + + } + + for trace in traces: + segments = [] + for segment in trace['segments']: + segments.append({ + 'Id': segment.id, + 'Document': segment.raw + }) + + result['Traces'].append({ + 'Duration': int((trace['end_date'] - trace['start_date']).total_seconds()), + 'Id': trace['trace_id'], + 'Segments': segments + }) + + return result + + +xray_backends = {} +for region, ec2_backend in ec2_backends.items(): + xray_backends[region] = XRayBackend() diff --git a/moto/xray/responses.py b/moto/xray/responses.py new file mode 100644 index 000000000..328a266bf --- /dev/null +++ b/moto/xray/responses.py @@ -0,0 +1,150 @@ +from __future__ import unicode_literals +import json +import datetime + +from moto.core.responses import BaseResponse +from six.moves.urllib.parse import urlsplit + +from .models import xray_backends +from .exceptions import AWSError, BadSegmentException + + +class XRayResponse(BaseResponse): + + def _error(self, code, message): + return json.dumps({'__type': code, 'message': message}), dict(status=400) + + @property + def xray_backend(self): + return xray_backends[self.region] + + @property + def request_params(self): + try: + return json.loads(self.body) + except ValueError: + return {} + + def _get_param(self, param, default=None): + return self.request_params.get(param, default) + + def _get_action(self): + # Amazon is just calling urls like /TelemetryRecords etc... + # This uses the value after / as the camalcase action, which then + # gets converted in call_action to find the following methods + return urlsplit(self.uri).path.lstrip('/') + + # PutTelemetryRecords + def telemetry_records(self): + try: + self.xray_backend.add_telemetry_records(self.request_params) + except AWSError as err: + return err.response() + + return '' + + # PutTraceSegments + def trace_segments(self): + docs = self._get_param('TraceSegmentDocuments') + + if docs is None: + msg = 'Parameter TraceSegmentDocuments is missing' + return json.dumps({'__type': 'MissingParameter', 'message': msg}), dict(status=400) + + # Raises an exception that contains info about a bad segment, + # the object also has a to_dict() method + bad_segments = [] + for doc in docs: + try: + self.xray_backend.process_segment(doc) + except BadSegmentException as bad_seg: + bad_segments.append(bad_seg) + except Exception as err: + return json.dumps({'__type': 'InternalFailure', 'message': str(err)}), dict(status=500) + + result = {'UnprocessedTraceSegments': [x.to_dict() for x in bad_segments]} + return json.dumps(result) + + # GetTraceSummaries + def trace_summaries(self): + start_time = self._get_param('StartTime') + end_time = self._get_param('EndTime') + if start_time is None: + msg = 'Parameter StartTime is missing' + return json.dumps({'__type': 'MissingParameter', 'message': msg}), dict(status=400) + if end_time is None: + msg = 'Parameter EndTime is missing' + return json.dumps({'__type': 'MissingParameter', 'message': msg}), dict(status=400) + + filter_expression = self._get_param('FilterExpression') + sampling = self._get_param('Sampling', 'false') == 'true' + + try: + start_time = datetime.datetime.fromtimestamp(int(start_time)) + end_time = datetime.datetime.fromtimestamp(int(end_time)) + except ValueError: + msg = 'start_time and end_time are not integers' + return json.dumps({'__type': 'InvalidParameterValue', 'message': msg}), dict(status=400) + except Exception as err: + return json.dumps({'__type': 'InternalFailure', 'message': str(err)}), dict(status=500) + + try: + result = self.xray_backend.get_trace_summary(start_time, end_time, filter_expression, sampling) + except AWSError as err: + return err.response() + except Exception as err: + return json.dumps({'__type': 'InternalFailure', 'message': str(err)}), dict(status=500) + + return json.dumps(result) + + # BatchGetTraces + def traces(self): + trace_ids = self._get_param('TraceIds') + next_token = self._get_param('NextToken') # not implemented yet + + if trace_ids is None: + msg = 'Parameter TraceIds is missing' + return json.dumps({'__type': 'MissingParameter', 'message': msg}), dict(status=400) + + try: + result = self.xray_backend.get_trace_ids(trace_ids, next_token) + except AWSError as err: + return err.response() + except Exception as err: + return json.dumps({'__type': 'InternalFailure', 'message': str(err)}), dict(status=500) + + return json.dumps(result) + + # GetServiceGraph - just a dummy response for now + def service_graph(self): + start_time = self._get_param('StartTime') + end_time = self._get_param('EndTime') + # next_token = self._get_param('NextToken') # not implemented yet + + if start_time is None: + msg = 'Parameter StartTime is missing' + return json.dumps({'__type': 'MissingParameter', 'message': msg}), dict(status=400) + if end_time is None: + msg = 'Parameter EndTime is missing' + return json.dumps({'__type': 'MissingParameter', 'message': msg}), dict(status=400) + + result = { + 'StartTime': start_time, + 'EndTime': end_time, + 'Services': [] + } + return json.dumps(result) + + # GetTraceGraph - just a dummy response for now + def trace_graph(self): + trace_ids = self._get_param('TraceIds') + # next_token = self._get_param('NextToken') # not implemented yet + + if trace_ids is None: + msg = 'Parameter TraceIds is missing' + return json.dumps({'__type': 'MissingParameter', 'message': msg}), dict(status=400) + + result = { + 'Services': [] + } + return json.dumps(result) diff --git a/moto/xray/urls.py b/moto/xray/urls.py new file mode 100644 index 000000000..b0f13a980 --- /dev/null +++ b/moto/xray/urls.py @@ -0,0 +1,15 @@ +from __future__ import unicode_literals +from .responses import XRayResponse + +url_bases = [ + "https?://xray.(.+).amazonaws.com", +] + +url_paths = { + '{0}/TelemetryRecords$': XRayResponse.dispatch, + '{0}/TraceSegments$': XRayResponse.dispatch, + '{0}/Traces$': XRayResponse.dispatch, + '{0}/ServiceGraph$': XRayResponse.dispatch, + '{0}/TraceGraph$': XRayResponse.dispatch, + '{0}/TraceSummaries$': XRayResponse.dispatch, +} diff --git a/requirements-dev.txt b/requirements-dev.txt index 52def6ed0..1c001305e 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -6,6 +6,12 @@ coverage flake8 freezegun flask +boto>=2.45.0 boto3>=1.4.4 -botocore>=1.4.28 -six +botocore>=1.5.77 +six>=1.9 +prompt-toolkit==1.0.14 +click==6.7 +inflection==0.3.1 +lxml==4.0.0 +beautifulsoup4==4.6.0 diff --git a/scripts/get_instance_info.py b/scripts/get_instance_info.py new file mode 100755 index 000000000..f883c0cae --- /dev/null +++ b/scripts/get_instance_info.py @@ -0,0 +1,150 @@ +#!/usr/bin/env python +import json +import os +import subprocess +import requests +from bs4 import BeautifulSoup + + +class Instance(object): + def __init__(self, instance): + self.instance = instance + + def _get_td(self, td): + return self.instance.find('td', attrs={'class': td}) + + def _get_sort(self, td): + return float(self.instance.find('td', attrs={'class': td}).find('span')['sort']) + + @property + def name(self): + return self._get_td('name').text.strip() + + @property + def apiname(self): + return self._get_td('apiname').text.strip() + + @property + def memory(self): + return self._get_sort('memory') + + @property + def computeunits(self): + return self._get_sort('computeunits') + + @property + def vcpus(self): + return self._get_sort('vcpus') + + @property + def gpus(self): + return int(self._get_td('gpus').text.strip()) + + @property + def fpga(self): + return int(self._get_td('fpga').text.strip()) + + @property + def ecu_per_vcpu(self): + return self._get_sort('ecu-per-vcpu') + + @property + def physical_processor(self): + return self._get_td('physical_processor').text.strip() + + @property + def clock_speed_ghz(self): + return self._get_td('clock_speed_ghz').text.strip() + + @property + def intel_avx(self): + return self._get_td('intel_avx').text.strip() + + @property + def intel_avx2(self): + return self._get_td('intel_avx2').text.strip() + + @property + def intel_turbo(self): + return self._get_td('intel_turbo').text.strip() + + @property + def storage(self): + return self._get_sort('storage') + + @property + def architecture(self): + return self._get_td('architecture').text.strip() + + @property + def network_perf(self): # 2 == low + return self._get_sort('networkperf') + + @property + def ebs_max_bandwidth(self): + return self._get_sort('ebs-max-bandwidth') + + @property + def ebs_throughput(self): + return self._get_sort('ebs-throughput') + + @property + def ebs_iops(self): + return self._get_sort('ebs-iops') + + @property + def max_ips(self): + return int(self._get_td('maxips').text.strip()) + + @property + def enhanced_networking(self): + return self._get_td('enhanced-networking').text.strip() != 'No' + + @property + def vpc_only(self): + return self._get_td('vpc-only').text.strip() != 'No' + + @property + def ipv6_support(self): + return self._get_td('ipv6-support').text.strip() != 'No' + + @property + def placement_group_support(self): + return self._get_td('placement-group-support').text.strip() != 'No' + + @property + def linux_virtualization(self): + return self._get_td('linux-virtualization').text.strip() + + def to_dict(self): + result = {} + + for attr in [x for x in self.__class__.__dict__.keys() if not x.startswith('_') and x != 'to_dict']: + result[attr] = getattr(self, attr) + + return self.apiname, result + + +def main(): + print("Getting HTML from http://www.ec2instances.info") + page_request = requests.get('http://www.ec2instances.info') + soup = BeautifulSoup(page_request.text, 'html.parser') + data_table = soup.find(id='data') + + print("Finding data in table") + instances = data_table.find('tbody').find_all('tr') + + print("Parsing data") + result = {} + for instance in instances: + instance_id, instance_data = Instance(instance).to_dict() + result[instance_id] = instance_data + + root_dir = subprocess.check_output(['git', 'rev-parse', '--show-toplevel']).decode().strip() + dest = os.path.join(root_dir, 'moto/ec2/resources/instance_types.json') + print("Writing data to {0}".format(dest)) + with open(dest, 'w') as open_file: + json.dump(result, open_file) + +if __name__ == '__main__': + main() diff --git a/scripts/implementation_coverage.py b/scripts/implementation_coverage.py new file mode 100755 index 000000000..f0d22fc95 --- /dev/null +++ b/scripts/implementation_coverage.py @@ -0,0 +1,69 @@ +#!/usr/bin/env python +import moto +from botocore import xform_name +from botocore.session import Session +import boto3 + + +def get_moto_implementation(service_name): + if not hasattr(moto, service_name): + return None + module = getattr(moto, service_name) + if module is None: + return None + mock = getattr(module, "mock_{}".format(service_name)) + if mock is None: + return None + backends = list(mock().backends.values()) + if backends: + return backends[0] + + +def calculate_implementation_coverage(): + service_names = Session().get_available_services() + coverage = {} + for service_name in service_names: + moto_client = get_moto_implementation(service_name) + real_client = boto3.client(service_name, region_name='us-east-1') + implemented = [] + not_implemented = [] + + operation_names = [xform_name(op) for op in real_client.meta.service_model.operation_names] + for op in operation_names: + if moto_client and op in dir(moto_client): + implemented.append(op) + else: + not_implemented.append(op) + + coverage[service_name] = { + 'implemented': implemented, + 'not_implemented': not_implemented, + } + return coverage + + +def print_implementation_coverage(): + coverage = calculate_implementation_coverage() + for service_name in coverage: + implemented = coverage.get(service_name)['implemented'] + not_implemented = coverage.get(service_name)['not_implemented'] + operations = sorted(implemented + not_implemented) + + if implemented and not_implemented: + percentage_implemented = int(100.0 * len(implemented) / (len(implemented) + len(not_implemented))) + elif implemented: + percentage_implemented = 100 + else: + percentage_implemented = 0 + + print("-----------------------") + print("{} - {}% implemented".format(service_name, percentage_implemented)) + print("-----------------------") + for op in operations: + if op in implemented: + print("[X] {}".format(op)) + else: + print("[ ] {}".format(op)) + +if __name__ == '__main__': + print_implementation_coverage() diff --git a/scripts/scaffold.py b/scripts/scaffold.py new file mode 100755 index 000000000..b1c9f3a0f --- /dev/null +++ b/scripts/scaffold.py @@ -0,0 +1,497 @@ +#!/usr/bin/env python +"""This script generates template codes and response body for specified boto3's operation and apply to appropriate files. +You only have to select service and operation that you want to add. +This script looks at the botocore's definition file of specified service and operation, and auto-generates codes and reponses. +Basically, this script supports almost all services, as long as its protocol is `query`, `json` or `rest-json`. +Event if aws adds new services, this script will work as long as the protocol is known. + +TODO: + - This scripts don't generates functions in `responses.py` for `rest-json`, because I don't know the rule of it. want someone fix this. + - In some services's operations, this scripts might crash. Make new issue on github then. +""" +import os +import re +import inspect +import importlib +from lxml import etree + +import click +import jinja2 +from prompt_toolkit import ( + prompt +) +from prompt_toolkit.contrib.completers import WordCompleter +from prompt_toolkit.shortcuts import print_tokens + +from botocore import xform_name +from botocore.session import Session +import boto3 + +from moto.core.responses import BaseResponse +from moto.core import BaseBackend +from implementation_coverage import ( + get_moto_implementation +) +from inflection import singularize + +TEMPLATE_DIR = os.path.join(os.path.dirname(__file__), './template') + +INPUT_IGNORED_IN_BACKEND = ['Marker', 'PageSize'] +OUTPUT_IGNORED_IN_BACKEND = ['NextMarker'] + + +def print_progress(title, body, color): + click.secho(u'\t{}\t'.format(title), fg=color, nl=False) + click.echo(body) + + +def select_service_and_operation(): + service_names = Session().get_available_services() + service_completer = WordCompleter(service_names) + service_name = prompt(u'Select service: ', completer=service_completer) + if service_name not in service_names: + click.secho(u'{} is not valid service'.format(service_name), fg='red') + raise click.Abort() + moto_client = get_moto_implementation(service_name) + real_client = boto3.client(service_name, region_name='us-east-1') + implemented = [] + not_implemented = [] + + operation_names = [xform_name(op) for op in real_client.meta.service_model.operation_names] + for op in operation_names: + if moto_client and op in dir(moto_client): + implemented.append(op) + else: + not_implemented.append(op) + operation_completer = WordCompleter(operation_names) + + click.echo('==Current Implementation Status==') + for operation_name in operation_names: + check = 'X' if operation_name in implemented else ' ' + click.secho('[{}] {}'.format(check, operation_name)) + click.echo('=================================') + operation_name = prompt(u'Select Operation: ', completer=operation_completer) + + if operation_name not in operation_names: + click.secho('{} is not valid operation'.format(operation_name), fg='red') + raise click.Abort() + + if operation_name in implemented: + click.secho('{} is already implemented'.format(operation_name), fg='red') + raise click.Abort() + return service_name, operation_name + + +def get_lib_dir(service): + return os.path.join('moto', service) + +def get_test_dir(service): + return os.path.join('tests', 'test_{}'.format(service)) + + +def render_template(tmpl_dir, tmpl_filename, context, service, alt_filename=None): + is_test = True if 'test' in tmpl_dir else False + rendered = jinja2.Environment( + loader=jinja2.FileSystemLoader(tmpl_dir) + ).get_template(tmpl_filename).render(context) + + dirname = get_test_dir(service) if is_test else get_lib_dir(service) + filename = alt_filename or os.path.splitext(tmpl_filename)[0] + filepath = os.path.join(dirname, filename) + + if os.path.exists(filepath): + print_progress('skip creating', filepath, 'yellow') + else: + print_progress('creating', filepath, 'green') + with open(filepath, 'w') as f: + f.write(rendered) + + +def append_mock_to_init_py(service): + path = os.path.join(os.path.dirname(__file__), '..', 'moto', '__init__.py') + with open(path) as f: + lines = [_.replace('\n', '') for _ in f.readlines()] + + if any(_ for _ in lines if re.match('^from.*mock_{}.*$'.format(service), _)): + return + filtered_lines = [_ for _ in lines if re.match('^from.*mock.*$', _)] + last_import_line_index = lines.index(filtered_lines[-1]) + + new_line = 'from .{} import mock_{} # flake8: noqa'.format(service, service) + lines.insert(last_import_line_index + 1, new_line) + + body = '\n'.join(lines) + '\n' + with open(path, 'w') as f: + f.write(body) + + +def append_mock_import_to_backends_py(service): + path = os.path.join(os.path.dirname(__file__), '..', 'moto', 'backends.py') + with open(path) as f: + lines = [_.replace('\n', '') for _ in f.readlines()] + + if any(_ for _ in lines if re.match('^from moto\.{}.*{}_backends.*$'.format(service, service), _)): + return + filtered_lines = [_ for _ in lines if re.match('^from.*backends.*$', _)] + last_import_line_index = lines.index(filtered_lines[-1]) + + new_line = 'from moto.{} import {}_backends'.format(service, service) + lines.insert(last_import_line_index + 1, new_line) + + body = '\n'.join(lines) + '\n' + with open(path, 'w') as f: + f.write(body) + +def append_mock_dict_to_backends_py(service): + path = os.path.join(os.path.dirname(__file__), '..', 'moto', 'backends.py') + with open(path) as f: + lines = [_.replace('\n', '') for _ in f.readlines()] + + # 'xray': xray_backends + if any(_ for _ in lines if re.match(".*'{}': {}_backends.*".format(service, service), _)): + return + filtered_lines = [_ for _ in lines if re.match(".*'.*':.*_backends.*", _)] + last_elem_line_index = lines.index(filtered_lines[-1]) + + new_line = " '{}': {}_backends,".format(service, service) + prev_line = lines[last_elem_line_index] + if not prev_line.endswith('{') and not prev_line.endswith(','): + lines[last_elem_line_index] += ',' + lines.insert(last_elem_line_index + 1, new_line) + + body = '\n'.join(lines) + '\n' + with open(path, 'w') as f: + f.write(body) + +def initialize_service(service, operation, api_protocol): + """create lib and test dirs if not exist + """ + lib_dir = os.path.join('moto', service) + test_dir = os.path.join('tests', 'test_{}'.format(service)) + + print_progress('Initializing service', service, 'green') + + client = boto3.client(service) + service_class = client.__class__.__name__ + endpoint_prefix = client._service_model.endpoint_prefix + + tmpl_context = { + 'service': service, + 'service_class': service_class, + 'endpoint_prefix': endpoint_prefix + } + + # initialize service directory + if os.path.exists(lib_dir): + print_progress('skip creating', lib_dir, 'yellow') + else: + print_progress('creating', lib_dir, 'green') + os.makedirs(lib_dir) + + tmpl_dir = os.path.join(TEMPLATE_DIR, 'lib') + for tmpl_filename in os.listdir(tmpl_dir): + render_template( + tmpl_dir, tmpl_filename, tmpl_context, service + ) + + # initialize test directory + if os.path.exists(test_dir): + print_progress('skip creating', test_dir, 'yellow') + else: + print_progress('creating', test_dir, 'green') + os.makedirs(test_dir) + tmpl_dir = os.path.join(TEMPLATE_DIR, 'test') + for tmpl_filename in os.listdir(tmpl_dir): + alt_filename = 'test_{}.py'.format(service) if tmpl_filename == 'test_service.py.j2' else None + render_template( + tmpl_dir, tmpl_filename, tmpl_context, service, alt_filename + ) + + # append mock to init files + append_mock_to_init_py(service) + append_mock_import_to_backends_py(service) + append_mock_dict_to_backends_py(service) + +def to_upper_camel_case(s): + return ''.join([_.title() for _ in s.split('_')]) + +def to_snake_case(s): + s1 = re.sub('(.)([A-Z][a-z]+)', r'\1_\2', s) + return re.sub('([a-z0-9])([A-Z])', r'\1_\2', s1).lower() + + +def get_function_in_responses(service, operation, protocol): + """refers to definition of API in botocore, and autogenerates function + You can see example of elbv2 from link below. + https://github.com/boto/botocore/blob/develop/botocore/data/elbv2/2015-12-01/service-2.json + """ + client = boto3.client(service) + + aws_operation_name = to_upper_camel_case(operation) + op_model = client._service_model.operation_model(aws_operation_name) + outputs = op_model.output_shape.members + inputs = op_model.input_shape.members + input_names = [to_snake_case(_) for _ in inputs.keys() if _ not in INPUT_IGNORED_IN_BACKEND] + output_names = [to_snake_case(_) for _ in outputs.keys() if _ not in OUTPUT_IGNORED_IN_BACKEND] + body = 'def {}(self):\n'.format(operation) + + for input_name, input_type in inputs.items(): + type_name = input_type.type_name + if type_name == 'integer': + arg_line_tmpl = ' {} = _get_int_param("{}")\n' + elif type_name == 'list': + arg_line_tmpl = ' {} = self._get_list_prefix("{}.member")\n' + else: + arg_line_tmpl = ' {} = self._get_param("{}")\n' + body += arg_line_tmpl.format(to_snake_case(input_name), input_name) + if output_names: + body += ' {} = self.{}_backend.{}(\n'.format(','.join(output_names), service, operation) + else: + body += ' self.{}_backend.{}(\n'.format(service, operation) + for input_name in input_names: + body += ' {}={},\n'.format(input_name, input_name) + + body += ' )\n' + if protocol == 'query': + body += ' template = self.response_template({}_TEMPLATE)\n'.format(operation.upper()) + body += ' return template.render({})\n'.format( + ','.join(['{}={}'.format(_, _) for _ in output_names]) + ) + elif protocol == 'json': + body += ' # TODO: adjust reponse\n' + body += ' return json.dumps({})\n'.format(','.join(['{}={}'.format(_, _) for _ in output_names])) + return body + + +def get_function_in_models(service, operation): + """refers to definition of API in botocore, and autogenerates function + You can see example of elbv2 from link below. + https://github.com/boto/botocore/blob/develop/botocore/data/elbv2/2015-12-01/service-2.json + """ + client = boto3.client(service) + aws_operation_name = to_upper_camel_case(operation) + op_model = client._service_model.operation_model(aws_operation_name) + inputs = op_model.input_shape.members + outputs = op_model.output_shape.members + input_names = [to_snake_case(_) for _ in inputs.keys() if _ not in INPUT_IGNORED_IN_BACKEND] + output_names = [to_snake_case(_) for _ in outputs.keys() if _ not in OUTPUT_IGNORED_IN_BACKEND] + if input_names: + body = 'def {}(self, {}):\n'.format(operation, ', '.join(input_names)) + else: + body = 'def {}(self)\n' + body += ' # implement here\n' + body += ' return {}\n'.format(', '.join(output_names)) + + return body + + +def _get_subtree(name, shape, replace_list, name_prefix=[]): + class_name = shape.__class__.__name__ + if class_name in ('StringShape', 'Shape'): + t = etree.Element(name) + if name_prefix: + t.text = '{{ %s.%s }}' % (name_prefix[-1], to_snake_case(name)) + else: + t.text = '{{ %s }}' % to_snake_case(name) + return t + elif class_name in ('ListShape', ): + replace_list.append((name, name_prefix)) + t = etree.Element(name) + t_member = etree.Element('member') + t.append(t_member) + for nested_name, nested_shape in shape.member.members.items(): + t_member.append(_get_subtree(nested_name, nested_shape, replace_list, name_prefix + [singularize(name.lower())])) + return t + raise ValueError('Not supported Shape') + + +def get_response_query_template(service, operation): + """refers to definition of API in botocore, and autogenerates template + Assume that response format is xml when protocol is query + + You can see example of elbv2 from link below. + https://github.com/boto/botocore/blob/develop/botocore/data/elbv2/2015-12-01/service-2.json + """ + client = boto3.client(service) + aws_operation_name = to_upper_camel_case(operation) + op_model = client._service_model.operation_model(aws_operation_name) + result_wrapper = op_model.output_shape.serialization['resultWrapper'] + response_wrapper = result_wrapper.replace('Result', 'Response') + metadata = op_model.metadata + xml_namespace = metadata['xmlNamespace'] + + # build xml tree + t_root = etree.Element(response_wrapper, xmlns=xml_namespace) + + # build metadata + t_metadata = etree.Element('ResponseMetadata') + t_request_id = etree.Element('RequestId') + t_request_id.text = '1549581b-12b7-11e3-895e-1334aEXAMPLE' + t_metadata.append(t_request_id) + t_root.append(t_metadata) + + # build result + t_result = etree.Element(result_wrapper) + outputs = op_model.output_shape.members + replace_list = [] + for output_name, output_shape in outputs.items(): + t_result.append(_get_subtree(output_name, output_shape, replace_list)) + t_root.append(t_result) + xml_body = etree.tostring(t_root, pretty_print=True).decode('utf-8') + xml_body_lines = xml_body.splitlines() + for replace in replace_list: + name = replace[0] + prefix = replace[1] + singular_name = singularize(name) + + start_tag = '<%s>' % name + iter_name = '{}.{}'.format(prefix[-1], name.lower())if prefix else name.lower() + loop_start = '{%% for %s in %s %%}' % (singular_name.lower(), iter_name) + end_tag = '' % name + loop_end = '{{ endfor }}' + + start_tag_indexes = [i for i, l in enumerate(xml_body_lines) if start_tag in l] + if len(start_tag_indexes) != 1: + raise Exception('tag %s not found in response body' % start_tag) + start_tag_index = start_tag_indexes[0] + xml_body_lines.insert(start_tag_index + 1, loop_start) + + end_tag_indexes = [i for i, l in enumerate(xml_body_lines) if end_tag in l] + if len(end_tag_indexes) != 1: + raise Exception('tag %s not found in response body' % end_tag) + end_tag_index = end_tag_indexes[0] + xml_body_lines.insert(end_tag_index, loop_end) + xml_body = '\n'.join(xml_body_lines) + body = '\n{}_TEMPLATE = """{}"""'.format(operation.upper(), xml_body) + return body + + +def insert_code_to_class(path, base_class, new_code): + with open(path) as f: + lines = [_.replace('\n', '') for _ in f.readlines()] + mod_path = os.path.splitext(path)[0].replace('/', '.') + mod = importlib.import_module(mod_path) + clsmembers = inspect.getmembers(mod, inspect.isclass) + _response_cls = [_[1] for _ in clsmembers if issubclass(_[1], base_class) and _[1] != base_class] + if len(_response_cls) != 1: + raise Exception('unknown error, number of clsmembers is not 1') + response_cls = _response_cls[0] + code_lines, line_no = inspect.getsourcelines(response_cls) + end_line_no = line_no + len(code_lines) + + func_lines = [' ' * 4 + _ for _ in new_code.splitlines()] + + lines = lines[:end_line_no] + func_lines + lines[end_line_no:] + + body = '\n'.join(lines) + '\n' + with open(path, 'w') as f: + f.write(body) + + +def insert_url(service, operation): + client = boto3.client(service) + service_class = client.__class__.__name__ + aws_operation_name = to_upper_camel_case(operation) + uri = client._service_model.operation_model(aws_operation_name).http['requestUri'] + + path = os.path.join(os.path.dirname(__file__), '..', 'moto', service, 'urls.py') + with open(path) as f: + lines = [_.replace('\n', '') for _ in f.readlines()] + + if any(_ for _ in lines if re.match(uri, _)): + return + + url_paths_found = False + last_elem_line_index = -1 + for i, line in enumerate(lines): + if line.startswith('url_paths'): + url_paths_found = True + if url_paths_found and line.startswith('}'): + last_elem_line_index = i - 1 + + prev_line = lines[last_elem_line_index] + if not prev_line.endswith('{') and not prev_line.endswith(','): + lines[last_elem_line_index] += ',' + + new_line = " '{0}%s$': %sResponse.dispatch," % ( + uri, service_class + ) + lines.insert(last_elem_line_index + 1, new_line) + + body = '\n'.join(lines) + '\n' + with open(path, 'w') as f: + f.write(body) + + +def insert_query_codes(service, operation): + func_in_responses = get_function_in_responses(service, operation, 'query') + func_in_models = get_function_in_models(service, operation) + template = get_response_query_template(service, operation) + + # edit responses.py + responses_path = 'moto/{}/responses.py'.format(service) + print_progress('inserting code', responses_path, 'green') + insert_code_to_class(responses_path, BaseResponse, func_in_responses) + + # insert template + with open(responses_path) as f: + lines = [_[:-1] for _ in f.readlines()] + lines += template.splitlines() + with open(responses_path, 'w') as f: + f.write('\n'.join(lines)) + + # edit models.py + models_path = 'moto/{}/models.py'.format(service) + print_progress('inserting code', models_path, 'green') + insert_code_to_class(models_path, BaseBackend, func_in_models) + + # edit urls.py + insert_url(service, operation) + +def insert_json_codes(service, operation): + func_in_responses = get_function_in_responses(service, operation, 'json') + func_in_models = get_function_in_models(service, operation) + + # edit responses.py + responses_path = 'moto/{}/responses.py'.format(service) + print_progress('inserting code', responses_path, 'green') + insert_code_to_class(responses_path, BaseResponse, func_in_responses) + + # edit models.py + models_path = 'moto/{}/models.py'.format(service) + print_progress('inserting code', models_path, 'green') + insert_code_to_class(models_path, BaseBackend, func_in_models) + + # edit urls.py + insert_url(service, operation) + +def insert_restjson_codes(service, operation): + func_in_models = get_function_in_models(service, operation) + + print_progress('skipping inserting code to responses.py', "dont't know how to implement", 'yellow') + # edit models.py + models_path = 'moto/{}/models.py'.format(service) + print_progress('inserting code', models_path, 'green') + insert_code_to_class(models_path, BaseBackend, func_in_models) + + # edit urls.py + insert_url(service, operation) + +@click.command() +def main(): + service, operation = select_service_and_operation() + api_protocol = boto3.client(service)._service_model.metadata['protocol'] + initialize_service(service, operation, api_protocol) + if api_protocol == 'query': + insert_query_codes(service, operation) + elif api_protocol == 'json': + insert_json_codes(service, operation) + elif api_protocol == 'rest-json': + insert_restjson_codes(service, operation) + else: + print_progress('skip inserting code', 'api protocol "{}" is not supported'.format(api_protocol), 'yellow') + + click.echo('You will still need to add the mock into "__init__.py"'.format(service)) + +if __name__ == '__main__': + main() diff --git a/scripts/template/lib/__init__.py.j2 b/scripts/template/lib/__init__.py.j2 new file mode 100644 index 000000000..8e5bf50c7 --- /dev/null +++ b/scripts/template/lib/__init__.py.j2 @@ -0,0 +1,7 @@ +from __future__ import unicode_literals +from .models import {{ service }}_backends +from ..core.models import base_decorator + +{{ service }}_backend = {{ service }}_backends['us-east-1'] +mock_{{ service }} = base_decorator({{ service }}_backends) + diff --git a/scripts/template/lib/exceptions.py.j2 b/scripts/template/lib/exceptions.py.j2 new file mode 100644 index 000000000..2e9a72b1a --- /dev/null +++ b/scripts/template/lib/exceptions.py.j2 @@ -0,0 +1,4 @@ +from __future__ import unicode_literals +from moto.core.exceptions import RESTError + + diff --git a/scripts/template/lib/models.py.j2 b/scripts/template/lib/models.py.j2 new file mode 100644 index 000000000..2a0097c1d --- /dev/null +++ b/scripts/template/lib/models.py.j2 @@ -0,0 +1,20 @@ +from __future__ import unicode_literals +import boto3 +from moto.core import BaseBackend, BaseModel + + +class {{ service_class }}Backend(BaseBackend): + def __init__(self, region_name=None): + super({{ service_class }}Backend, self).__init__() + self.region_name = region_name + + def reset(self): + region_name = self.region_name + self.__dict__ = {} + self.__init__(region_name) + + # add methods from here + + +available_regions = boto3.session.Session().get_available_regions("{{ service }}") +{{ service }}_backends = {region: {{ service_class }}Backend for region in available_regions} diff --git a/scripts/template/lib/responses.py.j2 b/scripts/template/lib/responses.py.j2 new file mode 100644 index 000000000..85827e651 --- /dev/null +++ b/scripts/template/lib/responses.py.j2 @@ -0,0 +1,15 @@ +from __future__ import unicode_literals +from moto.core.responses import BaseResponse +from .models import {{ service }}_backends + + +class {{ service_class }}Response(BaseResponse): + @property + def {{ service }}_backend(self): + return {{ service }}_backends[self.region] + + # add methods from here + + +# add templates from here + diff --git a/scripts/template/lib/urls.py.j2 b/scripts/template/lib/urls.py.j2 new file mode 100644 index 000000000..53cc03c0e --- /dev/null +++ b/scripts/template/lib/urls.py.j2 @@ -0,0 +1,9 @@ +from __future__ import unicode_literals +from .responses import {{ service_class }}Response + +url_bases = [ + "https?://{{ endpoint_prefix }}.(.+).amazonaws.com", +] + +url_paths = { +} diff --git a/scripts/template/test/test_server.py.j2 b/scripts/template/test/test_server.py.j2 new file mode 100644 index 000000000..f3963a743 --- /dev/null +++ b/scripts/template/test/test_server.py.j2 @@ -0,0 +1,16 @@ +from __future__ import unicode_literals + +import sure # noqa + +import moto.server as server +from moto import mock_{{ service }} + +''' +Test the different server responses +''' + +@mock_{{ service }} +def test_{{ service }}_list(): + backend = server.create_backend_app("{{ service }}") + test_client = backend.test_client() + # do test diff --git a/scripts/template/test/test_service.py.j2 b/scripts/template/test/test_service.py.j2 new file mode 100644 index 000000000..076f92e27 --- /dev/null +++ b/scripts/template/test/test_service.py.j2 @@ -0,0 +1,11 @@ +from __future__ import unicode_literals + +import boto3 +import sure # noqa +from moto import mock_{{ service }} + + +@mock_{{ service }} +def test_list(): + # do test + pass diff --git a/scripts/update_managed_policies.py b/scripts/update_managed_policies.py new file mode 100755 index 000000000..5b60660f6 --- /dev/null +++ b/scripts/update_managed_policies.py @@ -0,0 +1,63 @@ +#!/usr/bin/env python +# This updates our local copies of AWS' managed policies +# Invoked via `make update_managed_policies` +# +# Credit goes to +# https://gist.github.com/gene1wood/55b358748be3c314f956 + +from botocore.exceptions import NoCredentialsError +from datetime import datetime +import boto3 +import json +import sys + +output_file = "./moto/iam/aws_managed_policies.py" + + +def json_serial(obj): + """JSON serializer for objects not serializable by default json code""" + + if isinstance(obj, datetime): + serial = obj.isoformat() + return serial + raise TypeError("Type not serializable") + + +client = boto3.client('iam') + +policies = {} + +paginator = client.get_paginator('list_policies') +try: + response_iterator = paginator.paginate(Scope='AWS') + for response in response_iterator: + for policy in response['Policies']: + policies[policy['PolicyName']] = policy +except NoCredentialsError: + print("USAGE:") + print("Put your AWS credentials into ~/.aws/credentials and run:") + print(__file__) + print("") + print("Or specify them on the command line:") + print("AWS_ACCESS_KEY_ID=your_personal_access_key AWS_SECRET_ACCESS_KEY=your_personal_secret {}".format(__file__)) + print("") + sys.exit(1) + +for policy_name in policies: + response = client.get_policy_version( + PolicyArn=policies[policy_name]['Arn'], + VersionId=policies[policy_name]['DefaultVersionId']) + for key in response['PolicyVersion']: + policies[policy_name][key] = response['PolicyVersion'][key] + +with open(output_file, 'w') as f: + triple_quote = '\"\"\"' + + f.write("# Imported via `make aws_managed_policies`\n") + f.write('aws_managed_policies_data = {}\n'.format(triple_quote)) + f.write(json.dumps(policies, + sort_keys=True, + indent=4, + separators=(',', ': '), + default=json_serial)) + f.write('{}\n'.format(triple_quote)) diff --git a/setup.py b/setup.py old mode 100644 new mode 100755 index 289c1684c..378119925 --- a/setup.py +++ b/setup.py @@ -1,30 +1,43 @@ #!/usr/bin/env python from __future__ import unicode_literals +import setuptools from setuptools import setup, find_packages +import sys + install_requires = [ "Jinja2>=2.8", "boto>=2.36.0", "boto3>=1.2.1", "cookies", - "requests>=2.0", + "cryptography>=2.0.0", + "requests>=2.5", "xmltodict", "dicttoxml", - "six", + "six>1.9", "werkzeug", "pyaml", "pytz", - "python-dateutil", + "python-dateutil<3.0.0,>=2.1", "mock", + "docker>=2.5.1" ] extras_require = { 'server': ['flask'], } +# https://hynek.me/articles/conditional-python-dependencies/ +if int(setuptools.__version__.split(".", 1)[0]) < 18: + if sys.version_info[0:2] < (3, 3): + install_requires.append("backports.tempfile") +else: + extras_require[":python_version<'3.3'"] = ["backports.tempfile"] + + setup( name='moto', - version='1.0.1', + version='1.1.20', description='A library that allows your python tests to easily' ' mock out the boto library', author='Steve Pulec', @@ -38,6 +51,7 @@ setup( packages=find_packages(exclude=("tests", "tests.*")), install_requires=install_requires, extras_require=extras_require, + include_package_data=True, license="Apache", test_suite="tests", classifiers=[ @@ -45,6 +59,9 @@ setup( "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.3", + "Programming Language :: Python :: 3.4", + "Programming Language :: Python :: 3.5", + "Programming Language :: Python :: 3.6", "License :: OSI Approved :: Apache Software License", "Topic :: Software Development :: Testing", ], diff --git a/tests/test_acm/resources/README.md b/tests/test_acm/resources/README.md new file mode 100644 index 000000000..fa39f2d01 --- /dev/null +++ b/tests/test_acm/resources/README.md @@ -0,0 +1,40 @@ +# Simple CA and server cert generation + +Commands: +``` +openssl genrsa -out ca.key 4096 +openssl req -x509 -new -nodes -key ca.key -sha512 -days 3650 -out ca.pem +openssl genrsa -out star_moto_com.key 2048 +openssl req -new -key star_moto_com.key -out star_moto_com.csr +openssl x509 -req -in star_moto_com.csr -CA ca.pem -CAkey ca.key -CAcreateserial -out star_moto_com.pem -days 3650 -sha512 +``` + +Also appended GeoTrust cert to the ca.pem + +``` +-----BEGIN CERTIFICATE----- +MIIEKDCCAxCgAwIBAgIQAQAhJYiw+lmnd+8Fe2Yn3zANBgkqhkiG9w0BAQsFADBC +MQswCQYDVQQGEwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEbMBkGA1UEAxMS +R2VvVHJ1c3QgR2xvYmFsIENBMB4XDTE3MDUyMjExMzIzN1oXDTE4MTIzMTIzNTk1 +OVowSTELMAkGA1UEBhMCVVMxEzARBgNVBAoTCkdvb2dsZSBJbmMxJTAjBgNVBAMT +HEdvb2dsZSBJbnRlcm5ldCBBdXRob3JpdHkgRzIwggEiMA0GCSqGSIb3DQEBAQUA +A4IBDwAwggEKAoIBAQCcKgR3XNhQkToGo4Lg2FBIvIk/8RlwGohGfuCPxfGJziHu +Wv5hDbcyRImgdAtTT1WkzoJile7rWV/G4QWAEsRelD+8W0g49FP3JOb7kekVxM/0 +Uw30SvyfVN59vqBrb4fA0FAfKDADQNoIc1Fsf/86PKc3Bo69SxEE630k3ub5/DFx ++5TVYPMuSq9C0svqxGoassxT3RVLix/IGWEfzZ2oPmMrhDVpZYTIGcVGIvhTlb7j +gEoQxirsupcgEcc5mRAEoPBhepUljE5SdeK27QjKFPzOImqzTs9GA5eXA37Asd57 +r0Uzz7o+cbfe9CUlwg01iZ2d+w4ReYkeN8WvjnJpAgMBAAGjggERMIIBDTAfBgNV +HSMEGDAWgBTAephojYn7qwVkDBF9qn1luMrMTjAdBgNVHQ4EFgQUSt0GFhu89mi1 +dvWBtrtiGrpagS8wDgYDVR0PAQH/BAQDAgEGMC4GCCsGAQUFBwEBBCIwIDAeBggr +BgEFBQcwAYYSaHR0cDovL2cuc3ltY2QuY29tMBIGA1UdEwEB/wQIMAYBAf8CAQAw +NQYDVR0fBC4wLDAqoCigJoYkaHR0cDovL2cuc3ltY2IuY29tL2NybHMvZ3RnbG9i +YWwuY3JsMCEGA1UdIAQaMBgwDAYKKwYBBAHWeQIFATAIBgZngQwBAgIwHQYDVR0l +BBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMA0GCSqGSIb3DQEBCwUAA4IBAQDKSeWs +12Rkd1u+cfrP9B4jx5ppY1Rf60zWGSgjZGaOHMeHgGRfBIsmr5jfCnC8vBk97nsz +qX+99AXUcLsFJnnqmseYuQcZZTTMPOk/xQH6bwx+23pwXEz+LQDwyr4tjrSogPsB +E4jLnD/lu3fKOmc2887VJwJyQ6C9bgLxRwVxPgFZ6RGeGvOED4Cmong1L7bHon8X +fOGLVq7uZ4hRJzBgpWJSwzfVO+qFKgE4h6LPcK2kesnE58rF2rwjMvL+GMJ74N87 +L9TQEOaWTPtEtyFkDbkAlDASJodYmDkFOA/MgkgMCkdm7r+0X8T/cKjhf4t5K7hl +MqO5tzHpCvX2HzLc +-----END CERTIFICATE----- +``` diff --git a/tests/test_acm/resources/ca.key b/tests/test_acm/resources/ca.key new file mode 100644 index 000000000..dc3110483 --- /dev/null +++ b/tests/test_acm/resources/ca.key @@ -0,0 +1,51 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIJKAIBAAKCAgEAo8Yi3CUvZ/AEHjGz2o8HUX70xtWqmOE85uoyQHQAdGZHvu4S +0QPMAmr49t14PyNWo4+6BAYwgatHIMuGCPWMnmgyHQq6/Had5awhviF6MwnAGaSs +l4+YaVaCQLpPkaKVKtG16dLj49Q24uwy1d/XQnyKZpNWr5FrAayUizijfnUrLpJc +UtGnRGlSlfOxkvSiQ1tVFdVTjVQRkOGzJE2xbtVbJUu/RqyVgnKkIIl6SL4wJS+K +I5oDDLNuGgTm2ajpcMJw6fn5XYpV0eGgNxTLE+ao4x8+FGnWOvdBi3EiHf6W6w6M +v36bW8m4KAB7XH2f5DdkhmsICqhakfXlbJD2CYEg5MUUIoCSHGm6AL8YPYEOlG5l +9Sbv3ydzVn6oGKxlBN3m3LJQezT1KcfHKxBS9uDPjSHjVGjpocxZeejA0qtHwiKZ +bpkOS4GXMb/A0z+DtlN8R0m5XIOYbwNTiurNswH/XQ5dzH424KZ9drc9GTqqQNRB +hGaX3pSrnd4qKDk+WV2H9y/WaZrxAQS2IXDeiqtjZoF8c/n+k7BnELcz+cQ9hKjw +xKfge+Rx4YCCm1KVRk2w4ZojlmhCeexWOmNONRibRFAUgXxk5HUv74bEU5CSfr/z +CX9kgpxpe8WTj/MbNZtDLO/XewPSk1lnFhmyfMyiY0EbA0iqjlvzhUVr3IsCAwEA +AQKCAgAQWSD1tMiMqYrfsLpxYMGsQu4QQxfqduFrc4lcobfB5svWpVE/iA9/VkpP +6j+sncxyO1CoQi3pY72P6oEQt+I3ldMazw1nUjfky0/6+MCIA7snVCbeYjkmmroZ +1/9FXGNjiNeN5b1V6sMn18gjTVrhiikOoDqRAAUcf6u8UgUQBIYw+e85XTBDRfg2 +e8MIFl90NdPCgC789p1iRwVo5FCH7chRasRHO8cY5HS5wr9wL3wC2kIB18fiJq8g +7chVheT3mpFG6esYiUyTzN//X5X+AJJvSZka1I3MCQ6D6uEq+VT7VdJ68xpzCSnW +5GVsECY708O3H0wINFIGK/Og6L+GfnSafAcMWCfNqTtlqFkQw7I5SGd6rpJrylzx +dixR9tq5cpPf1L6lwr43SYgBfT4Kk6jOSh4YEruWxFhyKCyB87i9zNS6OLab1V4o +CTA3ePuhduq8pSf6ID4ko2kytVoxx5kQPVs7uCEbgAvqc0pYKp24WN0zf0ALsaUg +ef0IXDCkZ7kNEl5ySs+TW1KwCEMAbEcoLmchE7lZISIcCo7XAvhaj8zNOW2TMmmU +4QfS7IJQrL5sh1o7L9JGfXhwPJshZIfsLl7t2MAmRW9gKRG/HZyT0joxpNqKSn76 +sWrahRU/lJD8FgjexIb6Jo9Y2sdYQ55kFh9i0iubn12grsipkQKCAQEA05lFY1f3 +FPvAkldASEcT4utyyEinEdoUhxfbd6ge8GSjyrx3OpD+fXd9r8DH7VTeDUqdCjQ0 +xXjXVBvThBy3xW1iri1msPoS0rLY9KNsk+ewJjpu55kdIssAo85cq7u4Gj4OD4K+ +Ga2Ob9GB7m8i7Z+jsEe9I2zesQ0fx6+u4gSSzHdwSTTocyfXfNT3vpqxgbMzNLfh +6QvjZ0/oQik5cPOYRTpyPNpBv3puRMrrTEbi7/GeR1t8mmoj9Jx6La5l2ZpWhuEF +PEJRkPnkY1cHUMkSNq6+6dWFvxyxwQBFO9XpAbEfCTApIuUv+L7kQildhLvJuO1k +5GIXnRDMWA+p5QKCAQEAxiPMv/uMa4DX7UIUw5C1Sc8JwQHeyiTNzfTOQYywHuZO +PZxpCy2Bg8J29npBbz1XStErJTVqlSGvoLvej+UPEjOIo1wGIN2mfOHJmMgRZPj7 +0xb5ViCWti1tD2KBVzfkw8yVurVymFCELcXDgmoefaXF0JB/8lbyXJUYnVh+dVFj +UHCuhmK2FzL3zFu/sLzRIUCUIQuneh4XawIjL/qlVPUGmg1Pl3Jc5Nabb9BDndqr +F6iESnS6ojHvc9CxiIe0VjJJB5ypu/K2/LiuYPIak7KD08XmmccO0xN0JbLVotpd +gxY3QzOxdkObqsrPYam3n0umRNBes9w7xm2jY3xFrwKCAQAZ+Y2wBUNaexEHpdhJ +Rxhk2bxMY8bGhTvR3ZbeWG/72I7Wu03zjYsAAeQW6BZixvE/NnKSpxf3Sb89HvFX +HWNdw/DCKuaZQQmAfd3uIgWZHm7cMn4cxgnylHLuqM1tc2zFI+r78nO9mTWL+m71 +wwTJoLgqUpQgPiQUHeVR0Pop2p/eo6bQBcOnJzPnqgkDh9/UaRgXF5+OyRvQOdns +DT105SJDFUqit7Qsei4BGdvKkEUZaVKhtdRU7ESfqXnCE8+C59RJWGQZIpb6sgJc +Q3mtbBFlTww2jjSN4krbw1m8X6TrxT9nFFdoZjP+WAiTKprFSXwYzGN/OZ9mc4Jy +KPIVAoIBACiJanpcnH3h/kssGdNo564SDYzPNSVmIjTgwNHoVp/7vkYcmeEPjk/G +mVAT8w8vHYzQ/mK+au/X1Hat+Pq3gj3XDT1etmJC9qzWBMidJfHifqLRMHHOeQcM +hCOBo7SUWtk0Ie3w8WD4GBLFQxqLW3GZWL8y0Ppjj3IhjseiMz0NSaRLaWlVCKv2 +YXwNyUn/V0nWTHf2Sm1RerkJ1ukZ/nlDJ/acgowZeafXwDVABpVlB1vvviD9gLFu +Re8L40ZrfRmlcAt+obsyDP3nSsXKwmU1QIMzGdqcPwwwDrMnw01uH3OSN/wnt0ba +zh5DH+p7LnYIpBuwBbAGfrQ5+hOAeUECggEBAMtPtTu9gXr/pAJlxT34uHa5vnHf +dErlpJFBE0lc8rZyPWyAfL9j+ovPyjMVT0uFzoLfyxHx/vvb08TMuc2FngPk3s5j +GCv90bHKkgqNMQUOH9AtAr1VbfkI55CGZDlcqXPnLx2Q/BHvd0w2t35e0q3Wox9I +7+IqCM7S1RSwZjXQ9wk+MKnBVow8vXhDPs+txAj56RdmYlYQTlhcAWnAIpFX5W60 +40FxP0LluSNIKR9Omp33KLjbOXeXySaYbe6Bv2/XGhRz6XQTL/xoB1WJj+QGXVm8 +ZfwiH08nMFr7KvQZFv8WEn9/yX8TE3hp83GH6cSWRm+KabUjoUc6nNgFvmY= +-----END RSA PRIVATE KEY----- diff --git a/tests/test_acm/resources/ca.pem b/tests/test_acm/resources/ca.pem new file mode 100644 index 000000000..29c4b6d28 --- /dev/null +++ b/tests/test_acm/resources/ca.pem @@ -0,0 +1,58 @@ +-----BEGIN CERTIFICATE----- +MIIFmTCCA4GgAwIBAgIJAJEFhPHteB99MA0GCSqGSIb3DQEBDQUAMGMxCzAJBgNV +BAYTAkdCMRIwEAYDVQQIDAlCZXJrc2hpcmUxDzANBgNVBAcMBlNsb3VnaDETMBEG +A1UECgwKTW90b1NlcnZlcjELMAkGA1UECwwCUUExDTALBgNVBAMMBE1vdG8wHhcN +MTcwOTIxMjA1MTM0WhcNMjcwOTE5MjA1MTM0WjBjMQswCQYDVQQGEwJHQjESMBAG +A1UECAwJQmVya3NoaXJlMQ8wDQYDVQQHDAZTbG91Z2gxEzARBgNVBAoMCk1vdG9T +ZXJ2ZXIxCzAJBgNVBAsMAlFBMQ0wCwYDVQQDDARNb3RvMIICIjANBgkqhkiG9w0B +AQEFAAOCAg8AMIICCgKCAgEAo8Yi3CUvZ/AEHjGz2o8HUX70xtWqmOE85uoyQHQA +dGZHvu4S0QPMAmr49t14PyNWo4+6BAYwgatHIMuGCPWMnmgyHQq6/Had5awhviF6 +MwnAGaSsl4+YaVaCQLpPkaKVKtG16dLj49Q24uwy1d/XQnyKZpNWr5FrAayUizij +fnUrLpJcUtGnRGlSlfOxkvSiQ1tVFdVTjVQRkOGzJE2xbtVbJUu/RqyVgnKkIIl6 +SL4wJS+KI5oDDLNuGgTm2ajpcMJw6fn5XYpV0eGgNxTLE+ao4x8+FGnWOvdBi3Ei +Hf6W6w6Mv36bW8m4KAB7XH2f5DdkhmsICqhakfXlbJD2CYEg5MUUIoCSHGm6AL8Y +PYEOlG5l9Sbv3ydzVn6oGKxlBN3m3LJQezT1KcfHKxBS9uDPjSHjVGjpocxZeejA +0qtHwiKZbpkOS4GXMb/A0z+DtlN8R0m5XIOYbwNTiurNswH/XQ5dzH424KZ9drc9 +GTqqQNRBhGaX3pSrnd4qKDk+WV2H9y/WaZrxAQS2IXDeiqtjZoF8c/n+k7BnELcz ++cQ9hKjwxKfge+Rx4YCCm1KVRk2w4ZojlmhCeexWOmNONRibRFAUgXxk5HUv74bE +U5CSfr/zCX9kgpxpe8WTj/MbNZtDLO/XewPSk1lnFhmyfMyiY0EbA0iqjlvzhUVr +3IsCAwEAAaNQME4wHQYDVR0OBBYEFLVMZNPKo5ZWUjU/bH8lRPyWBOYRMB8GA1Ud +IwQYMBaAFLVMZNPKo5ZWUjU/bH8lRPyWBOYRMAwGA1UdEwQFMAMBAf8wDQYJKoZI +hvcNAQENBQADggIBAC5EmWeJIRkRZ47hm+Q6QLyiRvilrBYobwJsCEsUnfYut0+v +bX+1L39hvkFPK9gx1bta38ZeVVc2uwkC59FgVFyWwQG8FpFo5Urbxp1ErRwXBcbs +cjG/GubMYJ0aNUYRbV4phlIh1nXby4vqRGAGukvdzix5UO3HrnT/T/mOzdXtvZ0H +KjB7z+CT5m6fqB+vbnOnY8kJNvzl1oz22NAvGqNM32MA/7oFg9bfpLAuaHwsXxXj +5J2GfN82DaVvFvwJ1RYcvC1UsTm6b69YLrnMvimZ+kH4a9HNz7JZPEBrGg87EclN +QecwL0RvAYq2AN+u5bPJSa4eel3wnimfgaKqiVEebx6IcBeoCu4HEfz46AJ/mCoT +5Y+41t0RhpfawJWz4v7QuEf7lf7d0lvk27VmGWmAjQv3MrDIVpyPmSG73o5b9zos +i2aGClD2kn+YPY8/XoDUc8qFNhTxk/ey7xuUjwViKNDyprApT5yBTs7PazDN+JbK +/lLQJh2V1qq8utiCZhLGhZL353pCf56MNAB2MbVk5yyP+FhJ058ouQHerszeESTI +uuaSFKYdgOX9BHdEhCDebF3e9K3+6MeOgnfY12jzhX6dygQDcUAuIamLo5hEptBl +XD1cVBrMdxKLjxUVaYAWw2n8HBt97oMzrHhmr5JE4yIU2MYf2B5c0aewRrnG +-----END CERTIFICATE----- +-----BEGIN CERTIFICATE----- +MIIEKDCCAxCgAwIBAgIQAQAhJYiw+lmnd+8Fe2Yn3zANBgkqhkiG9w0BAQsFADBC +MQswCQYDVQQGEwJVUzEWMBQGA1UEChMNR2VvVHJ1c3QgSW5jLjEbMBkGA1UEAxMS +R2VvVHJ1c3QgR2xvYmFsIENBMB4XDTE3MDUyMjExMzIzN1oXDTE4MTIzMTIzNTk1 +OVowSTELMAkGA1UEBhMCVVMxEzARBgNVBAoTCkdvb2dsZSBJbmMxJTAjBgNVBAMT +HEdvb2dsZSBJbnRlcm5ldCBBdXRob3JpdHkgRzIwggEiMA0GCSqGSIb3DQEBAQUA +A4IBDwAwggEKAoIBAQCcKgR3XNhQkToGo4Lg2FBIvIk/8RlwGohGfuCPxfGJziHu +Wv5hDbcyRImgdAtTT1WkzoJile7rWV/G4QWAEsRelD+8W0g49FP3JOb7kekVxM/0 +Uw30SvyfVN59vqBrb4fA0FAfKDADQNoIc1Fsf/86PKc3Bo69SxEE630k3ub5/DFx ++5TVYPMuSq9C0svqxGoassxT3RVLix/IGWEfzZ2oPmMrhDVpZYTIGcVGIvhTlb7j +gEoQxirsupcgEcc5mRAEoPBhepUljE5SdeK27QjKFPzOImqzTs9GA5eXA37Asd57 +r0Uzz7o+cbfe9CUlwg01iZ2d+w4ReYkeN8WvjnJpAgMBAAGjggERMIIBDTAfBgNV +HSMEGDAWgBTAephojYn7qwVkDBF9qn1luMrMTjAdBgNVHQ4EFgQUSt0GFhu89mi1 +dvWBtrtiGrpagS8wDgYDVR0PAQH/BAQDAgEGMC4GCCsGAQUFBwEBBCIwIDAeBggr +BgEFBQcwAYYSaHR0cDovL2cuc3ltY2QuY29tMBIGA1UdEwEB/wQIMAYBAf8CAQAw +NQYDVR0fBC4wLDAqoCigJoYkaHR0cDovL2cuc3ltY2IuY29tL2NybHMvZ3RnbG9i +YWwuY3JsMCEGA1UdIAQaMBgwDAYKKwYBBAHWeQIFATAIBgZngQwBAgIwHQYDVR0l +BBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMA0GCSqGSIb3DQEBCwUAA4IBAQDKSeWs +12Rkd1u+cfrP9B4jx5ppY1Rf60zWGSgjZGaOHMeHgGRfBIsmr5jfCnC8vBk97nsz +qX+99AXUcLsFJnnqmseYuQcZZTTMPOk/xQH6bwx+23pwXEz+LQDwyr4tjrSogPsB +E4jLnD/lu3fKOmc2887VJwJyQ6C9bgLxRwVxPgFZ6RGeGvOED4Cmong1L7bHon8X +fOGLVq7uZ4hRJzBgpWJSwzfVO+qFKgE4h6LPcK2kesnE58rF2rwjMvL+GMJ74N87 +L9TQEOaWTPtEtyFkDbkAlDASJodYmDkFOA/MgkgMCkdm7r+0X8T/cKjhf4t5K7hl +MqO5tzHpCvX2HzLc +-----END CERTIFICATE----- + diff --git a/tests/test_acm/resources/ca.srl b/tests/test_acm/resources/ca.srl new file mode 100644 index 000000000..ba4789240 --- /dev/null +++ b/tests/test_acm/resources/ca.srl @@ -0,0 +1 @@ +DF5D91CC8A8FBAA0 diff --git a/tests/test_acm/resources/star_moto_com-bad.pem b/tests/test_acm/resources/star_moto_com-bad.pem new file mode 100644 index 000000000..e79c7d2e6 --- /dev/null +++ b/tests/test_acm/resources/star_moto_com-bad.pem @@ -0,0 +1,26 @@ +-----BEGIN CERTIFICATE----- +MIIEUDCCAjgCCQDfXZHMio+6oDANBgkqhkiG9w0BAQ0FADBjMQswCQYDVQQGEwJH +gbhdsgndthgngfdhujmnfhjmnftghjmQjESMBAGA1UECAwJQmVya3NoaXJlMQ8wDQYDVQQHDAZTbG91Z2gxEzARBgNVBAoM +Ck1vdG9TZXJ2ZXIxCzAJBgNVBAsMAlFBMQ0wCwYDVQQDDARNb3RvMB4XDTE3MDky +MTIxMjQ1MFoXDTI3MDkxOTIxMjQ1MFowcTELMAkGA1UEBhMCR0IxEjAQBgNVBAgM +CUJlcmtzaGlyZTEPMA0GA1UEBwwGU2xvdWdoMRMwEQYDVQQKDApNb3RvU2VydmVy +MRMwEQYDVQQLDApPcGVyYXRpb25zMRMwEQYDVQQDDAoqLm1vdG8uY29tMIIBIjAN +BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzC/oBkzwiIBEceSC/tSD7hkqs8AW +niDXbMgAQE9oxUxtkFESxiNa+EbAMLBFtBkPRvc3iKXh/cfLo7yP8VdqEIDmJCB/ +3T3ljjmrCMwquxYgZWMShnXZV0YfC19Vzq/gFpiyoaI2SI5NOFlfwhs5hFacTGkf +vpjJvf6HnrNJ7keQR+oGJNf7jVaCgOVdJ4lt7+98YDVde7jLx1DN+QbvViJQl60n +K3bmfuLiiw8154Eyi9DOcJE8AB+W7KpPdrmbPisR1EiqY0i0L62ZixN0rPi5hHF+ +ozwURL1axcmLjlhIFi8YhBCNcY6ThE7jrqgLIq1n6d8ezRxjDKmqfH1spQIDAQAB +MA0GCSqGSIb3DQEBDQUAA4ICAQCgl/EfjE0Jh3cqQgoOlaFq6L1iJVgy5sYKCC4r +OU4dHgifZ6/grqCJesGiS1Vh4L8XklN++C2aSL73lVtxXoCSopP8Yj0rOGeA6b+7 +Fetm4ZQYF61QtahC0L2fkvKXR+uz1I85ndSoMJPT8lbm7sYJuL81Si32NOo6kC6y +4eKzV4KznxdAf6XaQMKtMIyXO3PWTrjm5ayzS6UsmnBvULGDCaAQznFlVFdGNSHx +CaENICR0CBcB+vbL7FPC683a4afceM+aMcMVElWG5q8fxtgbL/aPhzfonhDGWOM4 +Rdg8x+yDdi7swxmWlcW5wlP8LpLxN/S3GR9j9IyelxUGmb20yTph3i1K6RM/Fm2W +PI8xdneA6qycUAJo93NfaCuNK7yBfK3uDLqmWlGh3xCG+I1JETLRbxYBWiqeVTb3 +qjHMrsgqTqjcaCiKR/5H2eVkdcr8mLxrV5niyBItDl1xGxj4LF8hDLormhaCjiBb +N1cMq5saj/BpoIanlqOWby6uRMYlZvuhwKQGPVWgfuRWKFzGbMWyPCxATbiU89Wb +IykNkT1zTCE/eZwH12T4A7jrBiWq8WNfIST0Z7MReE6Oz+M9Pxx7DyDzSb2Y1RmU +xNYd8CavZLCfns00xZSo+10deMoKVS9GgxSHcS4ELaVaBQwu35emiMJSLcK7iNGE +I4WVSA== +-----END CERTIFICATE----- diff --git a/tests/test_acm/resources/star_moto_com.csr b/tests/test_acm/resources/star_moto_com.csr new file mode 100644 index 000000000..9b745261f --- /dev/null +++ b/tests/test_acm/resources/star_moto_com.csr @@ -0,0 +1,17 @@ +-----BEGIN CERTIFICATE REQUEST----- +MIICtjCCAZ4CAQAwcTELMAkGA1UEBhMCR0IxEjAQBgNVBAgMCUJlcmtzaGlyZTEP +MA0GA1UEBwwGU2xvdWdoMRMwEQYDVQQKDApNb3RvU2VydmVyMRMwEQYDVQQLDApP +cGVyYXRpb25zMRMwEQYDVQQDDAoqLm1vdG8uY29tMIIBIjANBgkqhkiG9w0BAQEF +AAOCAQ8AMIIBCgKCAQEAzC/oBkzwiIBEceSC/tSD7hkqs8AWniDXbMgAQE9oxUxt +kFESxiNa+EbAMLBFtBkPRvc3iKXh/cfLo7yP8VdqEIDmJCB/3T3ljjmrCMwquxYg +ZWMShnXZV0YfC19Vzq/gFpiyoaI2SI5NOFlfwhs5hFacTGkfvpjJvf6HnrNJ7keQ +R+oGJNf7jVaCgOVdJ4lt7+98YDVde7jLx1DN+QbvViJQl60nK3bmfuLiiw8154Ey +i9DOcJE8AB+W7KpPdrmbPisR1EiqY0i0L62ZixN0rPi5hHF+ozwURL1axcmLjlhI +Fi8YhBCNcY6ThE7jrqgLIq1n6d8ezRxjDKmqfH1spQIDAQABoAAwDQYJKoZIhvcN +AQELBQADggEBAAioQDDifgKjJXhK9w0+dvTdw80cdc8Y4/vkkJe6fqR5i6qM6Nbk +FQt0YNy4dScU6/u+YBFRqRfSKS1QOh2Uq6pKoloHxlhf9gh/8aqjvgN3qy3Ncyya +D9pqlSe70NIHIIBB3EDyocTFtscEX4s8ysuGDxKysWsL57YrDCbVjliK6sRIDPOk +CqkQJXjbQdi4bwqE5iYgheQEFQV+uGpdsV7ZZi4E7KcFmIKk3PzattWUd8+bglPC +/rrzb97nRiz8J5XzoqrEPA+0ZCuQ6cvbbEOWggs5kMJe/MfihH0yGA5kIQNmTmRK +1PLqpTE6g293pgeBcWsuydIBB9pUmSMDT1I= +-----END CERTIFICATE REQUEST----- diff --git a/tests/test_acm/resources/star_moto_com.key b/tests/test_acm/resources/star_moto_com.key new file mode 100644 index 000000000..f8585a81e --- /dev/null +++ b/tests/test_acm/resources/star_moto_com.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEogIBAAKCAQEAzC/oBkzwiIBEceSC/tSD7hkqs8AWniDXbMgAQE9oxUxtkFES +xiNa+EbAMLBFtBkPRvc3iKXh/cfLo7yP8VdqEIDmJCB/3T3ljjmrCMwquxYgZWMS +hnXZV0YfC19Vzq/gFpiyoaI2SI5NOFlfwhs5hFacTGkfvpjJvf6HnrNJ7keQR+oG +JNf7jVaCgOVdJ4lt7+98YDVde7jLx1DN+QbvViJQl60nK3bmfuLiiw8154Eyi9DO +cJE8AB+W7KpPdrmbPisR1EiqY0i0L62ZixN0rPi5hHF+ozwURL1axcmLjlhIFi8Y +hBCNcY6ThE7jrqgLIq1n6d8ezRxjDKmqfH1spQIDAQABAoIBAECa588WiQSnkQB4 +TPpUQ2oSjHBTVtSxj3fb0DiI552FkSUYgdgvV5k2yZieLW/Ofgb2MZwK4HZrwQMN +pn22KtkN78N+hPZ7nyZhGLyv3NVVKurpbfMdVqdGiIwQnhXHkB+WMO7zZDmQzN4H +aUUBWDGHNez3VhP4Q9zZrA+Kqtm5OYmkDQYO6LqR+OQmqmLEeJOsbR9EUXDuhd5O +CyWkBwZP5JcmP985hZ7dGTZJ9ehFLYq6i6ZLmuSkt6QS/jf+AdLjd6b2b326CUwJ +xEf3ZwQ9b+BPZ+gCx91FsooRqa3NbFhvGJ34sN25xzppa5+IDDk5XZnXJugwq5Sg +t5f07AECgYEA/G3+GIXlnyLwOksFFHQp1yZIlXxeGhVZyDwSHkXcAwRnTWZHHftr +fZ2TQkyYxsySx/pP6PUHQDwhZKFSLIpc2Di2ZIUPZSNYrzEqCZIBTO9+2DBshjs6 +2tUyvpD68lZsQpjipD6wNF+308Px5hAg5mKr5IstHCcXkJcxa3v5kVMCgYEAzxM8 +PbGQmSNalcO1cBcj/f7sbEbJOtdb94ig8KRc8ImL3ZM9dJOugqc0EchMzUzFD4H/ +CjaC25CjxfBZSxV+0D6spUeLKogdwoyAM08/ZwD6BuMKZlbim84wV0VZBXjSaihq +qdaLnx0qC7/DPLf2zQfWkJCcqvPzMf+W6PgQcycCgYA3VW0jtwY0shXy0UsVxrj9 +Ppkem5qNIS0DJZfbJvkpeCek4cypF9niOU50dBHxUhrC12345O1n+UZgprQ6q0Ha +6+OfeUN8qhjgnmhWnLjIQp+NiF/htM4b9iwfdexsfuFQX+8ejddWQ70qIIPAKLzt +g6eme5Ox3ifePCZLJ2v3nQKBgFBeitb2/8Qv8IyH9PeYQ6PlOSWdI6TuyQb9xFkh +seC5wcsxxnxkhSq4coEkWIql7SXjsnToS0mkjavZaQ63PQzeBmvvpJfRVJuZpHhF +nboAqwnZPMQTnMgT8rcsdyykhCYnoZ5hYrdSvmro9oGudN+G10QsnGHNZOpW5N9u +yBOpAoGASb5aNQU9QFT8kyxZB+nKAuh6efa6HNMXMdEoYD9VOm0zPMRtorZdX4s4 +nYctHiIUmVAIXtkG0tR+cOelv2qKR5EfOo3HZtaP+fbOd0IykoZcbQJpc3PwDcCq +WgkRhN4dCVYD3ZXFYlUrCoDca7JE1KxmIbrlVSAaYilkt7UB3Qk= +-----END RSA PRIVATE KEY----- diff --git a/tests/test_acm/resources/star_moto_com.pem b/tests/test_acm/resources/star_moto_com.pem new file mode 100644 index 000000000..6d599d53e --- /dev/null +++ b/tests/test_acm/resources/star_moto_com.pem @@ -0,0 +1,26 @@ +-----BEGIN CERTIFICATE----- +MIIEUDCCAjgCCQDfXZHMio+6oDANBgkqhkiG9w0BAQ0FADBjMQswCQYDVQQGEwJH +QjESMBAGA1UECAwJQmVya3NoaXJlMQ8wDQYDVQQHDAZTbG91Z2gxEzARBgNVBAoM +Ck1vdG9TZXJ2ZXIxCzAJBgNVBAsMAlFBMQ0wCwYDVQQDDARNb3RvMB4XDTE3MDky +MTIxMjQ1MFoXDTI3MDkxOTIxMjQ1MFowcTELMAkGA1UEBhMCR0IxEjAQBgNVBAgM +CUJlcmtzaGlyZTEPMA0GA1UEBwwGU2xvdWdoMRMwEQYDVQQKDApNb3RvU2VydmVy +MRMwEQYDVQQLDApPcGVyYXRpb25zMRMwEQYDVQQDDAoqLm1vdG8uY29tMIIBIjAN +BgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAzC/oBkzwiIBEceSC/tSD7hkqs8AW +niDXbMgAQE9oxUxtkFESxiNa+EbAMLBFtBkPRvc3iKXh/cfLo7yP8VdqEIDmJCB/ +3T3ljjmrCMwquxYgZWMShnXZV0YfC19Vzq/gFpiyoaI2SI5NOFlfwhs5hFacTGkf +vpjJvf6HnrNJ7keQR+oGJNf7jVaCgOVdJ4lt7+98YDVde7jLx1DN+QbvViJQl60n +K3bmfuLiiw8154Eyi9DOcJE8AB+W7KpPdrmbPisR1EiqY0i0L62ZixN0rPi5hHF+ +ozwURL1axcmLjlhIFi8YhBCNcY6ThE7jrqgLIq1n6d8ezRxjDKmqfH1spQIDAQAB +MA0GCSqGSIb3DQEBDQUAA4ICAQCgl/EfjE0Jh3cqQgoOlaFq6L1iJVgy5sYKCC4r +OU4dHgifZ6/grqCJesGiS1Vh4L8XklN++C2aSL73lVtxXoCSopP8Yj0rOGeA6b+7 +Fetm4ZQYF61QtahC0L2fkvKXR+uz1I85ndSoMJPT8lbm7sYJuL81Si32NOo6kC6y +4eKzV4KznxdAf6XaQMKtMIyXO3PWTrjm5ayzS6UsmnBvULGDCaAQznFlVFdGNSHx +CaENICR0CBcB+vbL7FPC683a4afceM+aMcMVElWG5q8fxtgbL/aPhzfonhDGWOM4 +Rdg8x+yDdi7swxmWlcW5wlP8LpLxN/S3GR9j9IyelxUGmb20yTph3i1K6RM/Fm2W +PI8xdneA6qycUAJo93NfaCuNK7yBfK3uDLqmWlGh3xCG+I1JETLRbxYBWiqeVTb3 +qjHMrsgqTqjcaCiKR/5H2eVkdcr8mLxrV5niyBItDl1xGxj4LF8hDLormhaCjiBb +N1cMq5saj/BpoIanlqOWby6uRMYlZvuhwKQGPVWgfuRWKFzGbMWyPCxATbiU89Wb +IykNkT1zTCE/eZwH12T4A7jrBiWq8WNfIST0Z7MReE6Oz+M9Pxx7DyDzSb2Y1RmU +xNYd8CavZLCfns00xZSo+10deMoKVS9GgxSHcS4ELaVaBQwu35emiMJSLcK7iNGE +I4WVSA== +-----END CERTIFICATE----- diff --git a/tests/test_acm/test_acm.py b/tests/test_acm/test_acm.py new file mode 100644 index 000000000..96e362d1e --- /dev/null +++ b/tests/test_acm/test_acm.py @@ -0,0 +1,357 @@ +from __future__ import unicode_literals + +import os +import boto3 +from freezegun import freeze_time +import sure # noqa + +from botocore.exceptions import ClientError + +from moto import mock_acm + + +RESOURCE_FOLDER = os.path.join(os.path.dirname(__file__), 'resources') +_GET_RESOURCE = lambda x: open(os.path.join(RESOURCE_FOLDER, x), 'rb').read() +CA_CRT = _GET_RESOURCE('ca.pem') +CA_KEY = _GET_RESOURCE('ca.key') +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' + + +def _import_cert(client): + response = client.import_certificate( + Certificate=SERVER_CRT, + PrivateKey=SERVER_KEY, + CertificateChain=CA_CRT + ) + return response['CertificateArn'] + + +# Also tests GetCertificate +@mock_acm +def test_import_certificate(): + client = boto3.client('acm', region_name='eu-central-1') + + resp = client.import_certificate( + Certificate=SERVER_CRT, + PrivateKey=SERVER_KEY, + CertificateChain=CA_CRT + ) + resp = client.get_certificate(CertificateArn=resp['CertificateArn']) + + resp['Certificate'].should.equal(SERVER_CRT.decode()) + resp.should.contain('CertificateChain') + + +@mock_acm +def test_import_bad_certificate(): + client = boto3.client('acm', region_name='eu-central-1') + + try: + client.import_certificate( + Certificate=SERVER_CRT_BAD, + PrivateKey=SERVER_KEY, + ) + except ClientError as err: + err.response['Error']['Code'].should.equal('ValidationException') + else: + raise RuntimeError('Should of raised ValidationException') + + +@mock_acm +def test_list_certificates(): + client = boto3.client('acm', region_name='eu-central-1') + arn = _import_cert(client) + + resp = client.list_certificates() + len(resp['CertificateSummaryList']).should.equal(1) + + resp['CertificateSummaryList'][0]['CertificateArn'].should.equal(arn) + resp['CertificateSummaryList'][0]['DomainName'].should.equal(SERVER_COMMON_NAME) + + +@mock_acm +def test_get_invalid_certificate(): + client = boto3.client('acm', region_name='eu-central-1') + + try: + client.get_certificate(CertificateArn=BAD_ARN) + except ClientError as err: + err.response['Error']['Code'].should.equal('ResourceNotFoundException') + else: + raise RuntimeError('Should of raised ResourceNotFoundException') + + +# Also tests deleting invalid certificate +@mock_acm +def test_delete_certificate(): + client = boto3.client('acm', region_name='eu-central-1') + arn = _import_cert(client) + + # If it does not raise an error and the next call does, all is fine + client.delete_certificate(CertificateArn=arn) + + try: + client.delete_certificate(CertificateArn=arn) + except ClientError as err: + err.response['Error']['Code'].should.equal('ResourceNotFoundException') + else: + raise RuntimeError('Should of raised ResourceNotFoundException') + + +@mock_acm +def test_describe_certificate(): + client = boto3.client('acm', region_name='eu-central-1') + arn = _import_cert(client) + + resp = client.describe_certificate(CertificateArn=arn) + resp['Certificate']['CertificateArn'].should.equal(arn) + resp['Certificate']['DomainName'].should.equal(SERVER_COMMON_NAME) + resp['Certificate']['Issuer'].should.equal('Moto') + resp['Certificate']['KeyAlgorithm'].should.equal('RSA_2048') + resp['Certificate']['Status'].should.equal('ISSUED') + resp['Certificate']['Type'].should.equal('IMPORTED') + + +@mock_acm +def test_describe_certificate(): + client = boto3.client('acm', region_name='eu-central-1') + + try: + client.describe_certificate(CertificateArn=BAD_ARN) + except ClientError as err: + err.response['Error']['Code'].should.equal('ResourceNotFoundException') + else: + raise RuntimeError('Should of raised ResourceNotFoundException') + + +# Also tests ListTagsForCertificate +@mock_acm +def test_add_tags_to_certificate(): + client = boto3.client('acm', region_name='eu-central-1') + arn = _import_cert(client) + + client.add_tags_to_certificate( + CertificateArn=arn, + Tags=[ + {'Key': 'key1', 'Value': 'value1'}, + {'Key': 'key2'}, + ] + ) + + resp = client.list_tags_for_certificate(CertificateArn=arn) + tags = {item['Key']: item.get('Value', '__NONE__') for item in resp['Tags']} + + tags.should.contain('key1') + tags.should.contain('key2') + tags['key1'].should.equal('value1') + + # This way, it ensures that we can detect if None is passed back when it shouldnt, + # as we store keys without values with a value of None, but it shouldnt be passed back + tags['key2'].should.equal('__NONE__') + + +@mock_acm +def test_add_tags_to_invalid_certificate(): + client = boto3.client('acm', region_name='eu-central-1') + + try: + client.add_tags_to_certificate( + CertificateArn=BAD_ARN, + Tags=[ + {'Key': 'key1', 'Value': 'value1'}, + {'Key': 'key2'}, + ] + ) + except ClientError as err: + err.response['Error']['Code'].should.equal('ResourceNotFoundException') + else: + raise RuntimeError('Should of raised ResourceNotFoundException') + + +@mock_acm +def test_list_tags_for_invalid_certificate(): + client = boto3.client('acm', region_name='eu-central-1') + + try: + client.list_tags_for_certificate(CertificateArn=BAD_ARN) + except ClientError as err: + err.response['Error']['Code'].should.equal('ResourceNotFoundException') + else: + raise RuntimeError('Should of raised ResourceNotFoundException') + + +@mock_acm +def test_remove_tags_from_certificate(): + client = boto3.client('acm', region_name='eu-central-1') + arn = _import_cert(client) + + client.add_tags_to_certificate( + CertificateArn=arn, + Tags=[ + {'Key': 'key1', 'Value': 'value1'}, + {'Key': 'key2'}, + {'Key': 'key3', 'Value': 'value3'}, + {'Key': 'key4', 'Value': 'value4'}, + ] + ) + + client.remove_tags_from_certificate( + CertificateArn=arn, + Tags=[ + {'Key': 'key1', 'Value': 'value2'}, # Should not remove as doesnt match + {'Key': 'key2'}, # Single key removal + {'Key': 'key3', 'Value': 'value3'}, # Exact match removal + {'Key': 'key4'} # Partial match removal + ] + ) + + resp = client.list_tags_for_certificate(CertificateArn=arn) + tags = {item['Key']: item.get('Value', '__NONE__') for item in resp['Tags']} + + for key in ('key2', 'key3', 'key4'): + tags.should_not.contain(key) + + tags.should.contain('key1') + + +@mock_acm +def test_remove_tags_from_invalid_certificate(): + client = boto3.client('acm', region_name='eu-central-1') + + try: + client.remove_tags_from_certificate( + CertificateArn=BAD_ARN, + Tags=[ + {'Key': 'key1', 'Value': 'value1'}, + {'Key': 'key2'}, + ] + ) + except ClientError as err: + err.response['Error']['Code'].should.equal('ResourceNotFoundException') + else: + raise RuntimeError('Should of raised ResourceNotFoundException') + + +@mock_acm +def test_resend_validation_email(): + client = boto3.client('acm', region_name='eu-central-1') + arn = _import_cert(client) + + client.resend_validation_email( + CertificateArn=arn, + Domain='*.moto.com', + ValidationDomain='NOTUSEDYET' + ) + # Returns nothing, boto would raise Exceptions otherwise + + +@mock_acm +def test_resend_validation_email_invalid(): + client = boto3.client('acm', region_name='eu-central-1') + arn = _import_cert(client) + + try: + client.resend_validation_email( + CertificateArn=arn, + Domain='no-match.moto.com', + ValidationDomain='NOTUSEDYET' + ) + except ClientError as err: + err.response['Error']['Code'].should.equal('InvalidDomainValidationOptionsException') + else: + raise RuntimeError('Should of raised InvalidDomainValidationOptionsException') + + try: + client.resend_validation_email( + CertificateArn=BAD_ARN, + Domain='no-match.moto.com', + ValidationDomain='NOTUSEDYET' + ) + except ClientError as err: + err.response['Error']['Code'].should.equal('ResourceNotFoundException') + else: + raise RuntimeError('Should of raised ResourceNotFoundException') + + +@mock_acm +def test_request_certificate(): + client = boto3.client('acm', region_name='eu-central-1') + + resp = client.request_certificate( + DomainName='google.com', + SubjectAlternativeNames=['google.com', 'www.google.com', 'mail.google.com'], + ) + resp.should.contain('CertificateArn') + + +# # Also tests the SAN code +# # requires Pull: https://github.com/spulec/freezegun/pull/210 +# @freeze_time("2012-01-01 12:00:00", as_arg=True) +# @mock_acm +# def test_request_certificate(frozen_time): +# # After requesting a certificate, it should then auto-validate after 1 minute +# # Some sneaky programming for that ;-) +# client = boto3.client('acm', region_name='eu-central-1') +# +# resp = client.request_certificate( +# DomainName='google.com', +# SubjectAlternativeNames=['google.com', 'www.google.com', 'mail.google.com'], +# ) +# arn = resp['CertificateArn'] +# +# resp = client.describe_certificate(CertificateArn=arn) +# resp['Certificate']['CertificateArn'].should.equal(arn) +# resp['Certificate']['DomainName'].should.equal('google.com') +# resp['Certificate']['Issuer'].should.equal('Amazon') +# resp['Certificate']['KeyAlgorithm'].should.equal('RSA_2048') +# resp['Certificate']['Status'].should.equal('PENDING_VALIDATION') +# resp['Certificate']['Type'].should.equal('AMAZON_ISSUED') +# len(resp['Certificate']['SubjectAlternativeNames']).should.equal(3) +# +# # Move time +# frozen_time.move_to('2012-01-01 12:02:00') +# resp = client.describe_certificate(CertificateArn=arn) +# resp['Certificate']['CertificateArn'].should.equal(arn) +# resp['Certificate']['Status'].should.equal('ISSUED') +# +# +# # requires Pull: https://github.com/spulec/freezegun/pull/210 +# @freeze_time("2012-01-01 12:00:00", as_arg=True) +# @mock_acm +# def test_request_certificate(frozen_time): +# # After requesting a certificate, it should then auto-validate after 1 minute +# # Some sneaky programming for that ;-) +# client = boto3.client('acm', region_name='eu-central-1') +# +# resp = client.request_certificate( +# IdempotencyToken='test_token', +# DomainName='google.com', +# SubjectAlternativeNames=['google.com', 'www.google.com', 'mail.google.com'], +# ) +# original_arn = resp['CertificateArn'] +# +# # Should be able to request a certificate multiple times in an hour +# # after that it makes a new one +# for time_intervals in ('2012-01-01 12:15:00', '2012-01-01 12:30:00', '2012-01-01 12:45:00'): +# frozen_time.move_to(time_intervals) +# resp = client.request_certificate( +# IdempotencyToken='test_token', +# DomainName='google.com', +# SubjectAlternativeNames=['google.com', 'www.google.com', 'mail.google.com'], +# ) +# arn = resp['CertificateArn'] +# arn.should.equal(original_arn) +# +# # Move time +# frozen_time.move_to('2012-01-01 13:01:00') +# resp = client.request_certificate( +# IdempotencyToken='test_token', +# DomainName='google.com', +# SubjectAlternativeNames=['google.com', 'www.google.com', 'mail.google.com'], +# ) +# arn = resp['CertificateArn'] +# arn.should_not.equal(original_arn) diff --git a/tests/test_awslambda/test_lambda.py b/tests/test_awslambda/test_lambda.py index 007516f56..6b67ce0f0 100644 --- a/tests/test_awslambda/test_lambda.py +++ b/tests/test_awslambda/test_lambda.py @@ -12,11 +12,13 @@ import sure # noqa from freezegun import freeze_time from moto import mock_lambda, mock_s3, mock_ec2, settings +_lambda_region = 'us-east-1' if settings.TEST_SERVER_MODE else 'us-west-2' -def _process_lamda(pfunc): + +def _process_lambda(func_str): zip_output = io.BytesIO() zip_file = zipfile.ZipFile(zip_output, 'w', zipfile.ZIP_DEFLATED) - zip_file.writestr('lambda_function.zip', pfunc) + zip_file.writestr('lambda_function.py', func_str) zip_file.close() zip_output.seek(0) return zip_output.read() @@ -27,21 +29,23 @@ def get_test_zip_file1(): def lambda_handler(event, context): return event """ - return _process_lamda(pfunc) + return _process_lambda(pfunc) def get_test_zip_file2(): - pfunc = """ + func_str = """ +import boto3 + def lambda_handler(event, context): + ec2 = boto3.resource('ec2', region_name='us-west-2', endpoint_url='http://{base_url}') + volume_id = event.get('volume_id') - print('get volume details for %s' % volume_id) - import boto3 - ec2 = boto3.resource('ec2', region_name='us-west-2', endpoint_url="http://{base_url}") vol = ec2.Volume(volume_id) - print('Volume - %s state=%s, size=%s' % (volume_id, vol.state, vol.size)) + + print('get volume details for %s\\nVolume - %s state=%s, size=%s' % (volume_id, volume_id, vol.state, vol.size)) return event -""".format(base_url="localhost:5000" if settings.TEST_SERVER_MODE else "ec2.us-west-2.amazonaws.com") - return _process_lamda(pfunc) +""".format(base_url="motoserver:5000" if settings.TEST_SERVER_MODE else "ec2.us-west-2.amazonaws.com") + return _process_lambda(func_str) @mock_lambda @@ -58,7 +62,7 @@ def test_invoke_requestresponse_function(): FunctionName='testFunction', Runtime='python2.7', Role='test-iam-role', - Handler='lambda_function.handler', + Handler='lambda_function.lambda_handler', Code={ 'ZipFile': get_test_zip_file1(), }, @@ -73,10 +77,13 @@ def test_invoke_requestresponse_function(): Payload=json.dumps(in_data)) success_result["StatusCode"].should.equal(202) - base64.b64decode(success_result["LogResult"]).decode( - 'utf-8').should.equal(json.dumps(in_data)) - json.loads(success_result["Payload"].read().decode( - 'utf-8')).should.equal(in_data) + result_obj = json.loads( + base64.b64decode(success_result["LogResult"]).decode('utf-8')) + + result_obj.should.equal(in_data) + + payload = success_result["Payload"].read().decode('utf-8') + json.loads(payload).should.equal(in_data) @mock_lambda @@ -86,7 +93,7 @@ def test_invoke_event_function(): FunctionName='testFunction', Runtime='python2.7', Role='test-iam-role', - Handler='lambda_function.handler', + Handler='lambda_function.lambda_handler', Code={ 'ZipFile': get_test_zip_file1(), }, @@ -110,36 +117,47 @@ def test_invoke_event_function(): 'utf-8')).should.equal({}) -@mock_ec2 -@mock_lambda -def test_invoke_function_get_ec2_volume(): - conn = boto3.resource("ec2", "us-west-2") - vol = conn.create_volume(Size=99, AvailabilityZone='us-west-2') - vol = conn.Volume(vol.id) +if settings.TEST_SERVER_MODE: + @mock_ec2 + @mock_lambda + def test_invoke_function_get_ec2_volume(): + conn = boto3.resource("ec2", "us-west-2") + vol = conn.create_volume(Size=99, AvailabilityZone='us-west-2') + vol = conn.Volume(vol.id) - conn = boto3.client('lambda', 'us-west-2') - conn.create_function( - FunctionName='testFunction', - Runtime='python2.7', - Role='test-iam-role', - Handler='lambda_function.handler', - Code={ - 'ZipFile': get_test_zip_file2(), - }, - Description='test lambda function', - Timeout=3, - MemorySize=128, - Publish=True, - ) + conn = boto3.client('lambda', 'us-west-2') + conn.create_function( + FunctionName='testFunction', + Runtime='python2.7', + Role='test-iam-role', + Handler='lambda_function.lambda_handler', + Code={ + 'ZipFile': get_test_zip_file2(), + }, + Description='test lambda function', + Timeout=3, + MemorySize=128, + Publish=True, + ) - in_data = {'volume_id': vol.id} - result = conn.invoke(FunctionName='testFunction', - InvocationType='RequestResponse', Payload=json.dumps(in_data)) - result["StatusCode"].should.equal(202) - msg = 'get volume details for %s\nVolume - %s state=%s, size=%s\n%s' % ( - vol.id, vol.id, vol.state, vol.size, json.dumps(in_data)) - base64.b64decode(result["LogResult"]).decode('utf-8').should.equal(msg) - result['Payload'].read().decode('utf-8').should.equal(msg) + in_data = {'volume_id': vol.id} + result = conn.invoke(FunctionName='testFunction', + InvocationType='RequestResponse', Payload=json.dumps(in_data)) + result["StatusCode"].should.equal(202) + msg = 'get volume details for %s\nVolume - %s state=%s, size=%s\n%s' % ( + vol.id, vol.id, vol.state, vol.size, json.dumps(in_data)) + + log_result = base64.b64decode(result["LogResult"]).decode('utf-8') + + # fix for running under travis (TODO: investigate why it has an extra newline) + log_result = log_result.replace('\n\n', '\n') + log_result.should.equal(msg) + + payload = result['Payload'].read().decode('utf-8') + + # fix for running under travis (TODO: investigate why it has an extra newline) + payload = payload.replace('\n\n', '\n') + payload.should.equal(msg) @mock_lambda @@ -150,7 +168,7 @@ def test_create_based_on_s3_with_missing_bucket(): FunctionName='testFunction', Runtime='python2.7', Role='test-iam-role', - Handler='lambda_function.handler', + Handler='lambda_function.lambda_handler', Code={ 'S3Bucket': 'this-bucket-does-not-exist', 'S3Key': 'test.zip', @@ -181,7 +199,7 @@ def test_create_function_from_aws_bucket(): FunctionName='testFunction', Runtime='python2.7', Role='test-iam-role', - Handler='lambda_function.handler', + Handler='lambda_function.lambda_handler', Code={ 'S3Bucket': 'test-bucket', 'S3Key': 'test.zip', @@ -202,10 +220,10 @@ def test_create_function_from_aws_bucket(): result.pop('LastModified') result.should.equal({ 'FunctionName': 'testFunction', - 'FunctionArn': 'arn:aws:lambda:123456789012:function:testFunction', + 'FunctionArn': 'arn:aws:lambda:{}:123456789012:function:testFunction'.format(_lambda_region), 'Runtime': 'python2.7', 'Role': 'test-iam-role', - 'Handler': 'lambda_function.handler', + 'Handler': 'lambda_function.lambda_handler', "CodeSha256": hashlib.sha256(zip_content).hexdigest(), "CodeSize": len(zip_content), 'Description': 'test lambda function', @@ -230,7 +248,7 @@ def test_create_function_from_zipfile(): FunctionName='testFunction', Runtime='python2.7', Role='test-iam-role', - Handler='lambda_function.handler', + Handler='lambda_function.lambda_handler', Code={ 'ZipFile': zip_content, }, @@ -247,10 +265,10 @@ def test_create_function_from_zipfile(): result.should.equal({ 'FunctionName': 'testFunction', - 'FunctionArn': 'arn:aws:lambda:123456789012:function:testFunction', + 'FunctionArn': 'arn:aws:lambda:{}:123456789012:function:testFunction'.format(_lambda_region), 'Runtime': 'python2.7', 'Role': 'test-iam-role', - 'Handler': 'lambda_function.handler', + 'Handler': 'lambda_function.lambda_handler', 'CodeSize': len(zip_content), 'Description': 'test lambda function', 'Timeout': 3, @@ -281,7 +299,7 @@ def test_get_function(): FunctionName='testFunction', Runtime='python2.7', Role='test-iam-role', - Handler='lambda_function.handler', + Handler='lambda_function.lambda_handler', Code={ 'S3Bucket': 'test-bucket', 'S3Key': 'test.zip', @@ -301,16 +319,16 @@ def test_get_function(): result.should.equal({ "Code": { - "Location": "s3://lambda-functions.aws.amazon.com/test.zip", + "Location": "s3://awslambda-{0}-tasks.s3-{0}.amazonaws.com/test.zip".format(_lambda_region), "RepositoryType": "S3" }, "Configuration": { "CodeSha256": hashlib.sha256(zip_content).hexdigest(), "CodeSize": len(zip_content), "Description": "test lambda function", - "FunctionArn": "arn:aws:lambda:123456789012:function:testFunction", + "FunctionArn": 'arn:aws:lambda:{}:123456789012:function:testFunction'.format(_lambda_region), "FunctionName": "testFunction", - "Handler": "lambda_function.handler", + "Handler": "lambda_function.lambda_handler", "MemorySize": 128, "Role": "test-iam-role", "Runtime": "python2.7", @@ -339,7 +357,7 @@ def test_delete_function(): FunctionName='testFunction', Runtime='python2.7', Role='test-iam-role', - Handler='lambda_function.handler', + Handler='lambda_function.lambda_handler', Code={ 'S3Bucket': 'test-bucket', 'S3Key': 'test.zip', @@ -383,7 +401,7 @@ def test_list_create_list_get_delete_list(): FunctionName='testFunction', Runtime='python2.7', Role='test-iam-role', - Handler='lambda_function.handler', + Handler='lambda_function.lambda_handler', Code={ 'S3Bucket': 'test-bucket', 'S3Key': 'test.zip', @@ -395,16 +413,16 @@ def test_list_create_list_get_delete_list(): ) expected_function_result = { "Code": { - "Location": "s3://lambda-functions.aws.amazon.com/test.zip", + "Location": "s3://awslambda-{0}-tasks.s3-{0}.amazonaws.com/test.zip".format(_lambda_region), "RepositoryType": "S3" }, "Configuration": { "CodeSha256": hashlib.sha256(zip_content).hexdigest(), "CodeSize": len(zip_content), "Description": "test lambda function", - "FunctionArn": "arn:aws:lambda:123456789012:function:testFunction", + "FunctionArn": 'arn:aws:lambda:{}:123456789012:function:testFunction'.format(_lambda_region), "FunctionName": "testFunction", - "Handler": "lambda_function.handler", + "Handler": "lambda_function.lambda_handler", "MemorySize": 128, "Role": "test-iam-role", "Runtime": "python2.7", @@ -437,12 +455,12 @@ def test_list_create_list_get_delete_list(): @mock_lambda def test_invoke_lambda_error(): lambda_fx = """ - def lambda_handler(event, context): - raise Exception('failsauce') +def lambda_handler(event, context): + raise Exception('failsauce') """ zip_output = io.BytesIO() zip_file = zipfile.ZipFile(zip_output, 'w', zipfile.ZIP_DEFLATED) - zip_file.writestr('lambda_function.zip', lambda_fx) + zip_file.writestr('lambda_function.py', lambda_fx) zip_file.close() zip_output.seek(0) @@ -469,3 +487,161 @@ def test_invoke_lambda_error(): assert 'FunctionError' in result assert result['FunctionError'] == 'Handled' + +@mock_lambda +@mock_s3 +def test_tags(): + """ + test list_tags -> tag_resource -> list_tags -> tag_resource -> list_tags -> untag_resource -> list_tags integration + """ + s3_conn = boto3.client('s3', 'us-west-2') + s3_conn.create_bucket(Bucket='test-bucket') + + zip_content = get_test_zip_file2() + s3_conn.put_object(Bucket='test-bucket', Key='test.zip', Body=zip_content) + conn = boto3.client('lambda', 'us-west-2') + + function = conn.create_function( + FunctionName='testFunction', + Runtime='python2.7', + Role='test-iam-role', + Handler='lambda_function.handler', + Code={ + 'S3Bucket': 'test-bucket', + 'S3Key': 'test.zip', + }, + Description='test lambda function', + Timeout=3, + MemorySize=128, + Publish=True, + ) + + # List tags when there are none + conn.list_tags( + Resource=function['FunctionArn'] + )['Tags'].should.equal(dict()) + + # List tags when there is one + conn.tag_resource( + Resource=function['FunctionArn'], + Tags=dict(spam='eggs') + )['ResponseMetadata']['HTTPStatusCode'].should.equal(200) + conn.list_tags( + Resource=function['FunctionArn'] + )['Tags'].should.equal(dict(spam='eggs')) + + # List tags when another has been added + conn.tag_resource( + Resource=function['FunctionArn'], + Tags=dict(foo='bar') + )['ResponseMetadata']['HTTPStatusCode'].should.equal(200) + conn.list_tags( + Resource=function['FunctionArn'] + )['Tags'].should.equal(dict(spam='eggs', foo='bar')) + + # Untag resource + conn.untag_resource( + Resource=function['FunctionArn'], + TagKeys=['spam', 'trolls'] + )['ResponseMetadata']['HTTPStatusCode'].should.equal(204) + conn.list_tags( + Resource=function['FunctionArn'] + )['Tags'].should.equal(dict(foo='bar')) + + # Untag a tag that does not exist (no error and no change) + conn.untag_resource( + Resource=function['FunctionArn'], + TagKeys=['spam'] + )['ResponseMetadata']['HTTPStatusCode'].should.equal(204) + +@mock_lambda +def test_tags_not_found(): + """ + Test list_tags and tag_resource when the lambda with the given arn does not exist + """ + conn = boto3.client('lambda', 'us-west-2') + conn.list_tags.when.called_with( + Resource='arn:aws:lambda:123456789012:function:not-found' + ).should.throw(botocore.client.ClientError) + + conn.tag_resource.when.called_with( + Resource='arn:aws:lambda:123456789012:function:not-found', + 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'] + ).should.throw(botocore.client.ClientError) + +@mock_lambda +def test_invoke_async_function(): + conn = boto3.client('lambda', 'us-west-2') + conn.create_function( + FunctionName='testFunction', + Runtime='python2.7', + Role='test-iam-role', + Handler='lambda_function.handler', + Code={ + 'ZipFile': get_test_zip_file1(), + }, + Description='test lambda function', + Timeout=3, + MemorySize=128, + Publish=True, + ) + + success_result = conn.invoke_async( + FunctionName='testFunction', + InvokeArgs=json.dumps({ 'test': 'event' }) + ) + + success_result['Status'].should.equal(202) + +@mock_lambda +@freeze_time('2015-01-01 00:00:00') +def test_get_function_created_with_zipfile(): + conn = boto3.client('lambda', 'us-west-2') + zip_content = get_test_zip_file1() + result = conn.create_function( + FunctionName='testFunction', + Runtime='python2.7', + Role='test-iam-role', + Handler='lambda_function.handler', + Code={ + 'ZipFile': zip_content, + }, + Description='test lambda function', + Timeout=3, + MemorySize=128, + Publish=True, + ) + + response = conn.get_function( + FunctionName='testFunction' + ) + response['Configuration'].pop('LastModified') + + response['ResponseMetadata']['HTTPStatusCode'].should.equal(200) + assert len(response['Code']) == 2 + assert response['Code']['RepositoryType'] == 'S3' + assert response['Code']['Location'].startswith('s3://awslambda-{0}-tasks.s3-{0}.amazonaws.com'.format(_lambda_region)) + response['Configuration'].should.equal( + { + "CodeSha256": hashlib.sha256(zip_content).hexdigest(), + "CodeSize": len(zip_content), + "Description": "test lambda function", + "FunctionArn":'arn:aws:lambda:{}:123456789012:function:testFunction'.format(_lambda_region), + "FunctionName": "testFunction", + "Handler": "lambda_function.handler", + "MemorySize": 128, + "Role": "test-iam-role", + "Runtime": "python2.7", + "Timeout": 3, + "Version": '$LATEST', + "VpcConfig": { + "SecurityGroupIds": [], + "SubnetIds": [], + } + }, + ) diff --git a/tests/test_cloudformation/test_cloudformation_stack_crud_boto3.py b/tests/test_cloudformation/test_cloudformation_stack_crud_boto3.py index e428d1f63..ed2ee8337 100644 --- a/tests/test_cloudformation/test_cloudformation_stack_crud_boto3.py +++ b/tests/test_cloudformation/test_cloudformation_stack_crud_boto3.py @@ -39,6 +39,68 @@ dummy_template = { } } + +dummy_template_yaml = """--- +AWSTemplateFormatVersion: 2010-09-09 +Description: Stack1 with yaml template +Resources: + EC2Instance1: + Type: AWS::EC2::Instance + Properties: + ImageId: ami-d3adb33f + KeyName: dummy + InstanceType: t2.micro + Tags: + - Key: Description + Value: Test tag + - Key: Name + Value: Name tag for tests +""" + + +dummy_template_yaml_with_short_form_func = """--- +AWSTemplateFormatVersion: 2010-09-09 +Description: Stack1 with yaml template +Resources: + EC2Instance1: + Type: AWS::EC2::Instance + Properties: + ImageId: ami-d3adb33f + KeyName: !Join [ ":", [ du, m, my ] ] + InstanceType: t2.micro + Tags: + - Key: Description + Value: Test tag + - Key: Name + Value: Name tag for tests +""" + + +dummy_template_yaml_with_ref = """--- +AWSTemplateFormatVersion: 2010-09-09 +Description: Stack1 with yaml template +Parameters: + TagDescription: + Type: String + TagName: + Type: String + +Resources: + EC2Instance1: + Type: AWS::EC2::Instance + Properties: + ImageId: ami-d3adb33f + KeyName: dummy + InstanceType: t2.micro + Tags: + - Key: Description + Value: + Ref: TagDescription + - Key: Name + Value: !Ref TagName +""" + + dummy_update_template = { "AWSTemplateFormatVersion": "2010-09-09", "Parameters": { @@ -110,6 +172,46 @@ def test_boto3_create_stack(): cf_conn.get_template(StackName="test_stack")['TemplateBody'].should.equal( dummy_template) +@mock_cloudformation +def test_boto3_create_stack_with_yaml(): + cf_conn = boto3.client('cloudformation', region_name='us-east-1') + cf_conn.create_stack( + StackName="test_stack", + TemplateBody=dummy_template_yaml, + ) + + cf_conn.get_template(StackName="test_stack")['TemplateBody'].should.equal( + dummy_template_yaml) + + +@mock_cloudformation +def test_boto3_create_stack_with_short_form_func_yaml(): + cf_conn = boto3.client('cloudformation', region_name='us-east-1') + cf_conn.create_stack( + StackName="test_stack", + TemplateBody=dummy_template_yaml_with_short_form_func, + ) + + cf_conn.get_template(StackName="test_stack")['TemplateBody'].should.equal( + dummy_template_yaml_with_short_form_func) + + +@mock_cloudformation +def test_boto3_create_stack_with_ref_yaml(): + cf_conn = boto3.client('cloudformation', region_name='us-east-1') + params = [ + {'ParameterKey': 'TagDescription', 'ParameterValue': 'desc_ref'}, + {'ParameterKey': 'TagName', 'ParameterValue': 'name_ref'}, + ] + cf_conn.create_stack( + StackName="test_stack", + TemplateBody=dummy_template_yaml_with_ref, + Parameters=params + ) + + cf_conn.get_template(StackName="test_stack")['TemplateBody'].should.equal( + dummy_template_yaml_with_ref) + @mock_cloudformation def test_creating_stacks_across_regions(): @@ -150,7 +252,6 @@ def test_create_stack_with_role_arn(): TemplateBody=dummy_template_json, RoleARN='arn:aws:iam::123456789012:role/moto', ) - stack = list(cf.stacks.all())[0] stack.role_arn.should.equal('arn:aws:iam::123456789012:role/moto') diff --git a/tests/test_cloudformation/test_stack_parsing.py b/tests/test_cloudformation/test_stack_parsing.py index ee53e9a68..d9fe4d80d 100644 --- a/tests/test_cloudformation/test_stack_parsing.py +++ b/tests/test_cloudformation/test_stack_parsing.py @@ -10,8 +10,11 @@ from moto.cloudformation.models import FakeStack from moto.cloudformation.parsing import resource_class_from_type, parse_condition, Export from moto.sqs.models import Queue from moto.s3.models import FakeBucket +from moto.cloudformation.utils import yaml_tag_constructor from boto.cloudformation.stack import Output + + dummy_template = { "AWSTemplateFormatVersion": "2010-09-09", @@ -380,3 +383,47 @@ def test_import(): queue = import_stack.resource_map['Queue'] queue.name.should.equal("value") + + + +def test_short_form_func_in_yaml_teamplate(): + template = """--- + KeyB64: !Base64 valueToEncode + KeyRef: !Ref foo + KeyAnd: !And + - A + - B + KeyEquals: !Equals [A, B] + KeyIf: !If [A, B, C] + KeyNot: !Not [A] + KeyOr: !Or [A, B] + KeyFindInMap: !FindInMap [A, B, C] + KeyGetAtt: !GetAtt A.B + KeyGetAZs: !GetAZs A + KeyImportValue: !ImportValue A + KeyJoin: !Join [ ":", [A, B, C] ] + KeySelect: !Select [A, B] + KeySplit: !Split [A, B] + KeySub: !Sub A + """ + yaml.add_multi_constructor('', yaml_tag_constructor) + template_dict = yaml.load(template) + key_and_expects = [ + ['KeyRef', {'Ref': 'foo'}], + ['KeyB64', {'Fn::Base64': 'valueToEncode'}], + ['KeyAnd', {'Fn::And': ['A', 'B']}], + ['KeyEquals', {'Fn::Equals': ['A', 'B']}], + ['KeyIf', {'Fn::If': ['A', 'B', 'C']}], + ['KeyNot', {'Fn::Not': ['A']}], + ['KeyOr', {'Fn::Or': ['A', 'B']}], + ['KeyFindInMap', {'Fn::FindInMap': ['A', 'B', 'C']}], + ['KeyGetAtt', {'Fn::GetAtt': ['A', 'B']}], + ['KeyGetAZs', {'Fn::GetAZs': 'A'}], + ['KeyImportValue', {'Fn::ImportValue': 'A'}], + ['KeyJoin', {'Fn::Join': [ ":", [ 'A', 'B', 'C' ] ]}], + ['KeySelect', {'Fn::Select': ['A', 'B']}], + ['KeySplit', {'Fn::Split': ['A', 'B']}], + ['KeySub', {'Fn::Sub': 'A'}], + ] + for k, v in key_and_expects: + template_dict.should.have.key(k).which.should.be.equal(v) diff --git a/tests/test_cloudwatch/test_cloudwatch_boto3.py b/tests/test_cloudwatch/test_cloudwatch_boto3.py new file mode 100644 index 000000000..923ba0b75 --- /dev/null +++ b/tests/test_cloudwatch/test_cloudwatch_boto3.py @@ -0,0 +1,94 @@ +from __future__ import unicode_literals + +import boto3 +from botocore.exceptions import ClientError +import sure # noqa + +from moto import mock_cloudwatch + + +@mock_cloudwatch +def test_put_list_dashboard(): + client = boto3.client('cloudwatch', region_name='eu-central-1') + widget = '{"widgets": [{"type": "text", "x": 0, "y": 7, "width": 3, "height": 3, "properties": {"markdown": "Hello world"}}]}' + + client.put_dashboard(DashboardName='test1', DashboardBody=widget) + resp = client.list_dashboards() + + len(resp['DashboardEntries']).should.equal(1) + + +@mock_cloudwatch +def test_put_list_prefix_nomatch_dashboard(): + client = boto3.client('cloudwatch', region_name='eu-central-1') + widget = '{"widgets": [{"type": "text", "x": 0, "y": 7, "width": 3, "height": 3, "properties": {"markdown": "Hello world"}}]}' + + client.put_dashboard(DashboardName='test1', DashboardBody=widget) + resp = client.list_dashboards(DashboardNamePrefix='nomatch') + + len(resp['DashboardEntries']).should.equal(0) + + +@mock_cloudwatch +def test_delete_dashboard(): + client = boto3.client('cloudwatch', region_name='eu-central-1') + widget = '{"widgets": [{"type": "text", "x": 0, "y": 7, "width": 3, "height": 3, "properties": {"markdown": "Hello world"}}]}' + + client.put_dashboard(DashboardName='test1', DashboardBody=widget) + client.put_dashboard(DashboardName='test2', DashboardBody=widget) + client.put_dashboard(DashboardName='test3', DashboardBody=widget) + client.delete_dashboards(DashboardNames=['test2', 'test1']) + + resp = client.list_dashboards(DashboardNamePrefix='test3') + len(resp['DashboardEntries']).should.equal(1) + + +@mock_cloudwatch +def test_delete_dashboard_fail(): + client = boto3.client('cloudwatch', region_name='eu-central-1') + widget = '{"widgets": [{"type": "text", "x": 0, "y": 7, "width": 3, "height": 3, "properties": {"markdown": "Hello world"}}]}' + + client.put_dashboard(DashboardName='test1', DashboardBody=widget) + client.put_dashboard(DashboardName='test2', DashboardBody=widget) + client.put_dashboard(DashboardName='test3', DashboardBody=widget) + # Doesnt delete anything if all dashboards to be deleted do not exist + try: + client.delete_dashboards(DashboardNames=['test2', 'test1', 'test_no_match']) + except ClientError as err: + err.response['Error']['Code'].should.equal('ResourceNotFound') + else: + raise RuntimeError('Should of raised error') + + resp = client.list_dashboards() + len(resp['DashboardEntries']).should.equal(3) + + +@mock_cloudwatch +def test_get_dashboard(): + client = boto3.client('cloudwatch', region_name='eu-central-1') + widget = '{"widgets": [{"type": "text", "x": 0, "y": 7, "width": 3, "height": 3, "properties": {"markdown": "Hello world"}}]}' + client.put_dashboard(DashboardName='test1', DashboardBody=widget) + + resp = client.get_dashboard(DashboardName='test1') + resp.should.contain('DashboardArn') + resp.should.contain('DashboardBody') + resp['DashboardName'].should.equal('test1') + + +@mock_cloudwatch +def test_get_dashboard_fail(): + client = boto3.client('cloudwatch', region_name='eu-central-1') + + try: + client.get_dashboard(DashboardName='test1') + except ClientError as err: + err.response['Error']['Code'].should.equal('ResourceNotFound') + else: + raise RuntimeError('Should of raised error') + + + + + + + diff --git a/tests/test_dynamodb2/test_dynamodb.py b/tests/test_dynamodb2/test_dynamodb.py index 7fec5c2bd..35c14f396 100644 --- a/tests/test_dynamodb2/test_dynamodb.py +++ b/tests/test_dynamodb2/test_dynamodb.py @@ -9,6 +9,7 @@ from moto import mock_dynamodb2, mock_dynamodb2_deprecated from moto.dynamodb2 import dynamodb_backend2 from boto.exception import JSONResponseError from botocore.exceptions import ClientError +from boto3.dynamodb.conditions import Key from tests.helpers import requires_boto_gte import tests.backport_assert_raises from nose.tools import assert_raises @@ -149,3 +150,415 @@ def test_list_not_found_table_tags(): conn.list_tags_of_resource(ResourceArn=arn) except ClientError as exception: assert exception.response['Error']['Code'] == "ResourceNotFoundException" + + +@requires_boto_gte("2.9") +@mock_dynamodb2 +def test_item_add_empty_string_exception(): + name = 'TestTable' + conn = boto3.client('dynamodb', + region_name='us-west-2', + aws_access_key_id="ak", + aws_secret_access_key="sk") + conn.create_table(TableName=name, + KeySchema=[{'AttributeName':'forum_name','KeyType':'HASH'}], + AttributeDefinitions=[{'AttributeName':'forum_name','AttributeType':'S'}], + ProvisionedThroughput={'ReadCapacityUnits':5,'WriteCapacityUnits':5}) + + with assert_raises(ClientError) as ex: + conn.put_item( + TableName=name, + Item={ + 'forum_name': { 'S': 'LOLCat Forum' }, + 'subject': { 'S': 'Check this out!' }, + 'Body': { 'S': 'http://url_to_lolcat.gif'}, + 'SentBy': { 'S': "" }, + 'ReceivedTime': { 'S': '12/9/2011 11:36:03 PM'}, + } + ) + + ex.exception.response['Error']['Code'].should.equal('ValidationException') + ex.exception.response['ResponseMetadata']['HTTPStatusCode'].should.equal(400) + ex.exception.response['Error']['Message'].should.equal( + 'One or more parameter values were invalid: An AttributeValue may not contain an empty string' + ) + + +@requires_boto_gte("2.9") +@mock_dynamodb2 +def test_query_invalid_table(): + conn = boto3.client('dynamodb', + region_name='us-west-2', + aws_access_key_id="ak", + aws_secret_access_key="sk") + try: + conn.query(TableName='invalid_table', KeyConditionExpression='index1 = :partitionkeyval', ExpressionAttributeValues={':partitionkeyval': {'S':'test'}}) + except ClientError as exception: + assert exception.response['Error']['Code'] == "ResourceNotFoundException" + + +@requires_boto_gte("2.9") +@mock_dynamodb2 +def test_scan_returns_consumed_capacity(): + name = 'TestTable' + conn = boto3.client('dynamodb', + region_name='us-west-2', + aws_access_key_id="ak", + aws_secret_access_key="sk") + + conn.create_table(TableName=name, + KeySchema=[{'AttributeName':'forum_name','KeyType':'HASH'}], + AttributeDefinitions=[{'AttributeName':'forum_name','AttributeType':'S'}], + ProvisionedThroughput={'ReadCapacityUnits':5,'WriteCapacityUnits':5}) + + conn.put_item( + TableName=name, + Item={ + 'forum_name': { 'S': 'LOLCat Forum' }, + 'subject': { 'S': 'Check this out!' }, + 'Body': { 'S': 'http://url_to_lolcat.gif'}, + 'SentBy': { 'S': "test" }, + 'ReceivedTime': { 'S': '12/9/2011 11:36:03 PM'}, + } + ) + + response = conn.scan( + TableName=name, + ) + + assert 'ConsumedCapacity' in response + assert 'CapacityUnits' in response['ConsumedCapacity'] + assert response['ConsumedCapacity']['TableName'] == name + +@requires_boto_gte("2.9") +@mock_dynamodb2 +def test_query_returns_consumed_capacity(): + dynamodb = boto3.resource('dynamodb', region_name='us-east-1') + + # Create the DynamoDB table. + table = dynamodb.create_table( + TableName='users', + KeySchema=[ + { + 'AttributeName': 'forum_name', + 'KeyType': 'HASH' + }, + { + 'AttributeName': 'subject', + 'KeyType': 'RANGE' + }, + ], + AttributeDefinitions=[ + { + 'AttributeName': 'forum_name', + 'AttributeType': 'S' + }, + { + 'AttributeName': 'subject', + 'AttributeType': 'S' + }, + ], + ProvisionedThroughput={ + 'ReadCapacityUnits': 5, + 'WriteCapacityUnits': 5 + } + ) + table = dynamodb.Table('users') + + table.put_item(Item={ + 'forum_name': 'the-key', + 'subject': '123', + 'body': 'some test message' + }) + + results = table.query( + KeyConditionExpression=Key('forum_name').eq( + 'the-key') + ) + + assert 'ConsumedCapacity' in results + assert 'CapacityUnits' in results['ConsumedCapacity'] + assert results['ConsumedCapacity']['CapacityUnits'] == 1 + +@mock_dynamodb2 +def test_basic_projection_expressions(): + dynamodb = boto3.resource('dynamodb', region_name='us-east-1') + + # Create the DynamoDB table. + table = dynamodb.create_table( + TableName='users', + KeySchema=[ + { + 'AttributeName': 'forum_name', + 'KeyType': 'HASH' + }, + { + 'AttributeName': 'subject', + 'KeyType': 'RANGE' + }, + ], + AttributeDefinitions=[ + { + 'AttributeName': 'forum_name', + 'AttributeType': 'S' + }, + { + 'AttributeName': 'subject', + 'AttributeType': 'S' + }, + ], + ProvisionedThroughput={ + 'ReadCapacityUnits': 5, + 'WriteCapacityUnits': 5 + } + ) + table = dynamodb.Table('users') + + table.put_item(Item={ + 'forum_name': 'the-key', + 'subject': '123', + 'body': 'some test message' + }) + + table.put_item(Item={ + 'forum_name': 'not-the-key', + 'subject': '123', + 'body': 'some other test message' + }) + # Test a query returning all items + results = table.query( + KeyConditionExpression=Key('forum_name').eq( + 'the-key'), + ProjectionExpression='body, subject' + ) + + assert 'body' in results['Items'][0] + assert results['Items'][0]['body'] == 'some test message' + assert 'subject' in results['Items'][0] + + table.put_item(Item={ + 'forum_name': 'the-key', + 'subject': '1234', + 'body': 'yet another test message' + }) + + results = table.query( + KeyConditionExpression=Key('forum_name').eq( + 'the-key'), + ProjectionExpression='body' + ) + + assert 'body' in results['Items'][0] + assert results['Items'][0]['body'] == 'some test message' + assert 'body' in results['Items'][1] + assert results['Items'][1]['body'] == 'yet another test message' + +@mock_dynamodb2 +def test_basic_projection_expressions_with_attr_expression_names(): + dynamodb = boto3.resource('dynamodb', region_name='us-east-1') + + # Create the DynamoDB table. + table = dynamodb.create_table( + TableName='users', + KeySchema=[ + { + 'AttributeName': 'forum_name', + 'KeyType': 'HASH' + }, + { + 'AttributeName': 'subject', + 'KeyType': 'RANGE' + }, + ], + AttributeDefinitions=[ + { + 'AttributeName': 'forum_name', + 'AttributeType': 'S' + }, + { + 'AttributeName': 'subject', + 'AttributeType': 'S' + }, + ], + ProvisionedThroughput={ + 'ReadCapacityUnits': 5, + 'WriteCapacityUnits': 5 + } + ) + table = dynamodb.Table('users') + + table.put_item(Item={ + 'forum_name': 'the-key', + 'subject': '123', + 'body': 'some test message', + 'attachment': 'something' + }) + + table.put_item(Item={ + 'forum_name': 'not-the-key', + 'subject': '123', + 'body': 'some other test message', + 'attachment': 'something' + }) + # Test a query returning all items + + results = table.query( + KeyConditionExpression=Key('forum_name').eq( + 'the-key'), + ProjectionExpression='#rl, #rt, subject', + ExpressionAttributeNames={ + '#rl': 'body', + '#rt': 'attachment' + }, + ) + + assert 'body' in results['Items'][0] + assert results['Items'][0]['body'] == 'some test message' + assert 'subject' in results['Items'][0] + assert results['Items'][0]['subject'] == '123' + assert 'attachment' in results['Items'][0] + assert results['Items'][0]['attachment'] == 'something' + +@mock_dynamodb2 +def test_put_item_returns_consumed_capacity(): + dynamodb = boto3.resource('dynamodb', region_name='us-east-1') + + # Create the DynamoDB table. + table = dynamodb.create_table( + TableName='users', + KeySchema=[ + { + 'AttributeName': 'forum_name', + 'KeyType': 'HASH' + }, + { + 'AttributeName': 'subject', + 'KeyType': 'RANGE' + }, + ], + AttributeDefinitions=[ + { + 'AttributeName': 'forum_name', + 'AttributeType': 'S' + }, + { + 'AttributeName': 'subject', + 'AttributeType': 'S' + }, + ], + ProvisionedThroughput={ + 'ReadCapacityUnits': 5, + 'WriteCapacityUnits': 5 + } + ) + table = dynamodb.Table('users') + + response = table.put_item(Item={ + 'forum_name': 'the-key', + 'subject': '123', + 'body': 'some test message', + }) + + assert 'ConsumedCapacity' in response + +@mock_dynamodb2 +def test_update_item_returns_consumed_capacity(): + dynamodb = boto3.resource('dynamodb', region_name='us-east-1') + + # Create the DynamoDB table. + table = dynamodb.create_table( + TableName='users', + KeySchema=[ + { + 'AttributeName': 'forum_name', + 'KeyType': 'HASH' + }, + { + 'AttributeName': 'subject', + 'KeyType': 'RANGE' + }, + ], + AttributeDefinitions=[ + { + 'AttributeName': 'forum_name', + 'AttributeType': 'S' + }, + { + 'AttributeName': 'subject', + 'AttributeType': 'S' + }, + ], + ProvisionedThroughput={ + 'ReadCapacityUnits': 5, + 'WriteCapacityUnits': 5 + } + ) + table = dynamodb.Table('users') + + table.put_item(Item={ + 'forum_name': 'the-key', + 'subject': '123', + 'body': 'some test message', + }) + + response = table.update_item(Key={ + 'forum_name': 'the-key', + 'subject': '123' + }, + UpdateExpression='set body=:tb', + ExpressionAttributeValues={ + ':tb': 'a new message' + }) + + assert 'ConsumedCapacity' in response + assert 'CapacityUnits' in response['ConsumedCapacity'] + assert 'TableName' in response['ConsumedCapacity'] + +@mock_dynamodb2 +def test_get_item_returns_consumed_capacity(): + dynamodb = boto3.resource('dynamodb', region_name='us-east-1') + + # Create the DynamoDB table. + table = dynamodb.create_table( + TableName='users', + KeySchema=[ + { + 'AttributeName': 'forum_name', + 'KeyType': 'HASH' + }, + { + 'AttributeName': 'subject', + 'KeyType': 'RANGE' + }, + ], + AttributeDefinitions=[ + { + 'AttributeName': 'forum_name', + 'AttributeType': 'S' + }, + { + 'AttributeName': 'subject', + 'AttributeType': 'S' + }, + ], + ProvisionedThroughput={ + 'ReadCapacityUnits': 5, + 'WriteCapacityUnits': 5 + } + ) + table = dynamodb.Table('users') + + table.put_item(Item={ + 'forum_name': 'the-key', + 'subject': '123', + 'body': 'some test message', + }) + + response = table.get_item(Key={ + 'forum_name': 'the-key', + 'subject': '123' + }) + + assert 'ConsumedCapacity' in response + assert 'CapacityUnits' in response['ConsumedCapacity'] + assert 'TableName' in response['ConsumedCapacity'] diff --git a/tests/test_dynamodb2/test_dynamodb_table_with_range_key.py b/tests/test_dynamodb2/test_dynamodb_table_with_range_key.py index e4a586cbb..a9ab298b7 100644 --- a/tests/test_dynamodb2/test_dynamodb_table_with_range_key.py +++ b/tests/test_dynamodb2/test_dynamodb_table_with_range_key.py @@ -5,6 +5,7 @@ from decimal import Decimal import boto import boto3 from boto3.dynamodb.conditions import Key +from botocore.exceptions import ClientError import sure # noqa from freezegun import freeze_time from moto import mock_dynamodb2, mock_dynamodb2_deprecated @@ -1190,6 +1191,14 @@ def _create_table_with_range_key(): 'AttributeName': 'subject', 'AttributeType': 'S' }, + { + 'AttributeName': 'username', + 'AttributeType': 'S' + }, + { + 'AttributeName': 'created', + 'AttributeType': 'N' + } ], ProvisionedThroughput={ 'ReadCapacityUnits': 5, @@ -1306,6 +1315,36 @@ def test_update_item_add_value(): }) +@mock_dynamodb2 +def test_update_item_add_value_string_set(): + table = _create_table_with_range_key() + + table.put_item(Item={ + 'forum_name': 'the-key', + 'subject': '123', + 'string_set': set(['str1', 'str2']), + }) + + item_key = {'forum_name': 'the-key', 'subject': '123'} + table.update_item( + Key=item_key, + AttributeUpdates={ + 'string_set': { + 'Action': u'ADD', + 'Value': set(['str3']), + }, + }, + ) + + returned_item = dict((k, str(v) if isinstance(v, Decimal) else v) + for k, v in table.get_item(Key=item_key)['Item'].items()) + dict(returned_item).should.equal({ + 'string_set': set(['str1', 'str2', 'str3']), + 'forum_name': 'the-key', + 'subject': '123', + }) + + @mock_dynamodb2 def test_update_item_add_value_does_not_exist_is_created(): table = _create_table_with_range_key() @@ -1362,6 +1401,155 @@ def test_update_item_with_expression(): 'subject': '123', }) +@mock_dynamodb2 +def test_update_item_add_with_expression(): + table = _create_table_with_range_key() + + item_key = {'forum_name': 'the-key', 'subject': '123'} + current_item = { + 'forum_name': 'the-key', + 'subject': '123', + 'str_set': {'item1', 'item2', 'item3'}, + 'num_set': {1, 2, 3}, + 'num_val': 6 + } + + # Put an entry in the DB to play with + table.put_item(Item=current_item) + + # Update item to add a string value to a string set + table.update_item( + Key=item_key, + UpdateExpression='ADD str_set :v', + ExpressionAttributeValues={ + ':v': {'item4'} + } + ) + current_item['str_set'] = current_item['str_set'].union({'item4'}) + dict(table.get_item(Key=item_key)['Item']).should.equal(current_item) + + # Update item to add a num value to a num set + table.update_item( + Key=item_key, + UpdateExpression='ADD num_set :v', + ExpressionAttributeValues={ + ':v': {6} + } + ) + current_item['num_set'] = current_item['num_set'].union({6}) + dict(table.get_item(Key=item_key)['Item']).should.equal(current_item) + + # Update item to add a value to a number value + table.update_item( + Key=item_key, + UpdateExpression='ADD num_val :v', + ExpressionAttributeValues={ + ':v': 20 + } + ) + current_item['num_val'] = current_item['num_val'] + 20 + dict(table.get_item(Key=item_key)['Item']).should.equal(current_item) + + # Attempt to add a number value to a string set, should raise Client Error + table.update_item.when.called_with( + Key=item_key, + UpdateExpression='ADD str_set :v', + ExpressionAttributeValues={ + ':v': 20 + } + ).should.have.raised(ClientError) + dict(table.get_item(Key=item_key)['Item']).should.equal(current_item) + + # Attempt to add a number set to the string set, should raise a ClientError + table.update_item.when.called_with( + Key=item_key, + UpdateExpression='ADD str_set :v', + ExpressionAttributeValues={ + ':v': { 20 } + } + ).should.have.raised(ClientError) + dict(table.get_item(Key=item_key)['Item']).should.equal(current_item) + + # Attempt to update with a bad expression + table.update_item.when.called_with( + Key=item_key, + UpdateExpression='ADD str_set bad_value' + ).should.have.raised(ClientError) + + # Attempt to add a string value instead of a string set + table.update_item.when.called_with( + Key=item_key, + UpdateExpression='ADD str_set :v', + ExpressionAttributeValues={ + ':v': 'new_string' + } + ).should.have.raised(ClientError) + + +@mock_dynamodb2 +def test_update_item_delete_with_expression(): + table = _create_table_with_range_key() + + item_key = {'forum_name': 'the-key', 'subject': '123'} + current_item = { + 'forum_name': 'the-key', + 'subject': '123', + 'str_set': {'item1', 'item2', 'item3'}, + 'num_set': {1, 2, 3}, + 'num_val': 6 + } + + # Put an entry in the DB to play with + table.put_item(Item=current_item) + + # Update item to delete a string value from a string set + table.update_item( + Key=item_key, + UpdateExpression='DELETE str_set :v', + ExpressionAttributeValues={ + ':v': {'item2'} + } + ) + current_item['str_set'] = current_item['str_set'].difference({'item2'}) + dict(table.get_item(Key=item_key)['Item']).should.equal(current_item) + + # Update item to delete a num value from a num set + table.update_item( + Key=item_key, + UpdateExpression='DELETE num_set :v', + ExpressionAttributeValues={ + ':v': {2} + } + ) + current_item['num_set'] = current_item['num_set'].difference({2}) + dict(table.get_item(Key=item_key)['Item']).should.equal(current_item) + + # Try to delete on a number, this should fail + table.update_item.when.called_with( + Key=item_key, + UpdateExpression='DELETE num_val :v', + ExpressionAttributeValues={ + ':v': 20 + } + ).should.have.raised(ClientError) + dict(table.get_item(Key=item_key)['Item']).should.equal(current_item) + + # Try to delete a string set from a number set + table.update_item.when.called_with( + Key=item_key, + UpdateExpression='DELETE num_set :v', + ExpressionAttributeValues={ + ':v': {'del_str'} + } + ).should.have.raised(ClientError) + dict(table.get_item(Key=item_key)['Item']).should.equal(current_item) + + # Attempt to update with a bad expression + table.update_item.when.called_with( + Key=item_key, + UpdateExpression='DELETE num_val badvalue' + ).should.have.raised(ClientError) + @mock_dynamodb2 def test_boto3_query_gsi_range_comparison(): diff --git a/tests/test_ec2/test_amis.py b/tests/test_ec2/test_amis.py index ed251f527..cf9f73f0e 100755 --- a/tests/test_ec2/test_amis.py +++ b/tests/test_ec2/test_amis.py @@ -57,6 +57,10 @@ def test_ami_create_and_delete(): "Auto-created snapshot for AMI {0}".format(image.id)) snapshot.volume_id.should.equal(volume.id) + # root device should be in AMI's block device mappings + root_mapping = image.block_device_mapping.get(image.root_device_name) + root_mapping.should_not.be.none + # Deregister with assert_raises(EC2ResponseError) as ex: success = conn.deregister_image(image_id, dry_run=True) diff --git a/tests/test_ec2/test_elastic_block_store.py b/tests/test_ec2/test_elastic_block_store.py index b238e68f9..4427d4843 100644 --- a/tests/test_ec2/test_elastic_block_store.py +++ b/tests/test_ec2/test_elastic_block_store.py @@ -83,6 +83,12 @@ def test_filter_volume_by_id(): vol2 = conn.get_all_volumes(volume_ids=[volume1.id, volume2.id]) vol2.should.have.length_of(2) + with assert_raises(EC2ResponseError) as cm: + conn.get_all_volumes(volume_ids=['vol-does_not_exist']) + cm.exception.code.should.equal('InvalidVolume.NotFound') + cm.exception.status.should.equal(400) + cm.exception.request_id.should_not.be.none + @mock_ec2_deprecated def test_volume_filters(): @@ -302,6 +308,12 @@ def test_filter_snapshot_by_id(): s.volume_id.should.be.within([volume2.id, volume3.id]) s.region.name.should.equal(conn.region.name) + with assert_raises(EC2ResponseError) as cm: + conn.get_all_snapshots(snapshot_ids=['snap-does_not_exist']) + cm.exception.code.should.equal('InvalidSnapshot.NotFound') + cm.exception.status.should.equal(400) + cm.exception.request_id.should_not.be.none + @mock_ec2_deprecated def test_snapshot_filters(): diff --git a/tests/test_ec2/test_elastic_ip_addresses.py b/tests/test_ec2/test_elastic_ip_addresses.py index 2e1ae189a..709bdc33b 100644 --- a/tests/test_ec2/test_elastic_ip_addresses.py +++ b/tests/test_ec2/test_elastic_ip_addresses.py @@ -180,13 +180,31 @@ def test_eip_boto3_vpc_association(): 'SubnetId': subnet_res['Subnet']['SubnetId'] })[0] allocation_id = client.allocate_address(Domain='vpc')['AllocationId'] + address = service.VpcAddress(allocation_id) + address.load() + address.association_id.should.be.none + address.instance_id.should.be.empty + address.network_interface_id.should.be.empty association_id = client.associate_address( InstanceId=instance.id, AllocationId=allocation_id, AllowReassociation=False) instance.load() + address.reload() + address.association_id.should_not.be.none instance.public_ip_address.should_not.be.none instance.public_dns_name.should_not.be.none + address.network_interface_id.should.equal(instance.network_interfaces_attribute[0].get('NetworkInterfaceId')) + address.public_ip.should.equal(instance.public_ip_address) + address.instance_id.should.equal(instance.id) + + client.disassociate_address(AssociationId=address.association_id) + instance.reload() + address.reload() + instance.public_ip_address.should.be.none + address.network_interface_id.should.be.empty + address.association_id.should.be.none + address.instance_id.should.be.empty @mock_ec2_deprecated @@ -402,3 +420,84 @@ def test_eip_describe_none(): cm.exception.code.should.equal('InvalidAddress.NotFound') cm.exception.status.should.equal(400) cm.exception.request_id.should_not.be.none + + +@mock_ec2 +def test_eip_filters(): + service = boto3.resource('ec2', region_name='us-west-1') + client = boto3.client('ec2', region_name='us-west-1') + vpc_res = client.create_vpc(CidrBlock='10.0.0.0/24') + subnet_res = client.create_subnet( + VpcId=vpc_res['Vpc']['VpcId'], CidrBlock='10.0.0.0/24') + + def create_inst_with_eip(): + instance = service.create_instances(**{ + 'InstanceType': 't2.micro', + 'ImageId': 'ami-test', + 'MinCount': 1, + 'MaxCount': 1, + 'SubnetId': subnet_res['Subnet']['SubnetId'] + })[0] + allocation_id = client.allocate_address(Domain='vpc')['AllocationId'] + _ = client.associate_address( + InstanceId=instance.id, + AllocationId=allocation_id, + AllowReassociation=False) + instance.load() + address = service.VpcAddress(allocation_id) + address.load() + return instance, address + + inst1, eip1 = create_inst_with_eip() + inst2, eip2 = create_inst_with_eip() + inst3, eip3 = create_inst_with_eip() + + # Param search by AllocationId + addresses = list(service.vpc_addresses.filter(AllocationIds=[eip2.allocation_id])) + len(addresses).should.be.equal(1) + addresses[0].public_ip.should.equal(eip2.public_ip) + inst2.public_ip_address.should.equal(addresses[0].public_ip) + + # Param search by PublicIp + addresses = list(service.vpc_addresses.filter(PublicIps=[eip3.public_ip])) + len(addresses).should.be.equal(1) + addresses[0].public_ip.should.equal(eip3.public_ip) + inst3.public_ip_address.should.equal(addresses[0].public_ip) + + # Param search by Filter + def check_vpc_filter_valid(filter_name, filter_values): + addresses = list(service.vpc_addresses.filter( + Filters=[{'Name': filter_name, + 'Values': filter_values}])) + len(addresses).should.equal(2) + ips = [addr.public_ip for addr in addresses] + set(ips).should.equal(set([eip1.public_ip, eip2.public_ip])) + ips.should.contain(inst1.public_ip_address) + + def check_vpc_filter_invalid(filter_name): + addresses = list(service.vpc_addresses.filter( + Filters=[{'Name': filter_name, + 'Values': ['dummy1', 'dummy2']}])) + len(addresses).should.equal(0) + + def check_vpc_filter(filter_name, filter_values): + check_vpc_filter_valid(filter_name, filter_values) + check_vpc_filter_invalid(filter_name) + + check_vpc_filter('allocation-id', [eip1.allocation_id, eip2.allocation_id]) + check_vpc_filter('association-id', [eip1.association_id, eip2.association_id]) + check_vpc_filter('instance-id', [inst1.id, inst2.id]) + check_vpc_filter( + 'network-interface-id', + [inst1.network_interfaces_attribute[0].get('NetworkInterfaceId'), + inst2.network_interfaces_attribute[0].get('NetworkInterfaceId')]) + check_vpc_filter( + 'private-ip-address', + [inst1.network_interfaces_attribute[0].get('PrivateIpAddress'), + inst2.network_interfaces_attribute[0].get('PrivateIpAddress')]) + check_vpc_filter('public-ip', [inst1.public_ip_address, inst2.public_ip_address]) + + # all the ips are in a VPC + addresses = list(service.vpc_addresses.filter( + Filters=[{'Name': 'domain', 'Values': ['vpc']}])) + len(addresses).should.equal(3) diff --git a/tests/test_ec2/test_general.py b/tests/test_ec2/test_general.py index 1dc77df82..4c319d30d 100644 --- a/tests/test_ec2/test_general.py +++ b/tests/test_ec2/test_general.py @@ -4,10 +4,11 @@ import tests.backport_assert_raises from nose.tools import assert_raises import boto +import boto3 from boto.exception import EC2ResponseError import sure # noqa -from moto import mock_ec2_deprecated +from moto import mock_ec2_deprecated, mock_ec2 @mock_ec2_deprecated @@ -15,7 +16,6 @@ def test_console_output(): conn = boto.connect_ec2('the_key', 'the_secret') reservation = conn.run_instances('ami-1234abcd') instance_id = reservation.instances[0].id - output = conn.get_console_output(instance_id) output.output.should_not.equal(None) @@ -29,3 +29,14 @@ def test_console_output_without_instance(): cm.exception.code.should.equal('InvalidInstanceID.NotFound') cm.exception.status.should.equal(400) cm.exception.request_id.should_not.be.none + + +@mock_ec2 +def test_console_output_boto3(): + conn = boto3.resource('ec2', 'us-east-1') + instances = conn.create_instances(ImageId='ami-1234abcd', + MinCount=1, + MaxCount=1) + + output = instances[0].console_output() + output.get('Output').should_not.equal(None) diff --git a/tests/test_ec2/test_instances.py b/tests/test_ec2/test_instances.py index cb953f8cf..04e6a6daa 100644 --- a/tests/test_ec2/test_instances.py +++ b/tests/test_ec2/test_instances.py @@ -378,11 +378,15 @@ def test_get_instances_filtering_by_vpc_id(): reservations1.should.have.length_of(1) reservations1[0].instances.should.have.length_of(1) reservations1[0].instances[0].id.should.equal(instance1.id) + reservations1[0].instances[0].vpc_id.should.equal(vpc1.id) + reservations1[0].instances[0].subnet_id.should.equal(subnet1.id) reservations2 = conn.get_all_instances(filters={'vpc-id': vpc2.id}) reservations2.should.have.length_of(1) reservations2[0].instances.should.have.length_of(1) reservations2[0].instances[0].id.should.equal(instance2.id) + reservations2[0].instances[0].vpc_id.should.equal(vpc2.id) + reservations2[0].instances[0].subnet_id.should.equal(subnet2.id) @mock_ec2_deprecated @@ -409,6 +413,68 @@ def test_get_instances_filtering_by_image_id(): 'Values': [image_id]}])['Reservations'] reservations[0]['Instances'].should.have.length_of(1) +@mock_ec2 +def test_get_instances_filtering_by_private_dns(): + image_id = 'ami-1234abcd' + client = boto3.client('ec2', region_name='us-east-1') + conn = boto3.resource('ec2', 'us-east-1') + conn.create_instances(ImageId=image_id, + MinCount=1, + MaxCount=1, + PrivateIpAddress='10.0.0.1') + reservations = client.describe_instances(Filters=[ + {'Name': 'private-dns-name', 'Values': ['ip-10-0-0-1.ec2.internal']} + ])['Reservations'] + reservations[0]['Instances'].should.have.length_of(1) + +@mock_ec2 +def test_get_instances_filtering_by_ni_private_dns(): + image_id = 'ami-1234abcd' + client = boto3.client('ec2', region_name='us-west-2') + conn = boto3.resource('ec2', 'us-west-2') + conn.create_instances(ImageId=image_id, + MinCount=1, + MaxCount=1, + PrivateIpAddress='10.0.0.1') + reservations = client.describe_instances(Filters=[ + {'Name': 'network-interface.private-dns-name', 'Values': ['ip-10-0-0-1.us-west-2.compute.internal']} + ])['Reservations'] + reservations[0]['Instances'].should.have.length_of(1) + +@mock_ec2 +def test_get_instances_filtering_by_instance_group_name(): + image_id = 'ami-1234abcd' + client = boto3.client('ec2', region_name='us-east-1') + client.create_security_group( + Description='test', + GroupName='test_sg' + ) + client.run_instances(ImageId=image_id, + MinCount=1, + MaxCount=1, + SecurityGroups=['test_sg']) + reservations = client.describe_instances(Filters=[ + {'Name': 'instance.group-name', 'Values': ['test_sg']} + ])['Reservations'] + reservations[0]['Instances'].should.have.length_of(1) + +@mock_ec2 +def test_get_instances_filtering_by_instance_group_id(): + image_id = 'ami-1234abcd' + client = boto3.client('ec2', region_name='us-east-1') + create_sg = client.create_security_group( + Description='test', + GroupName='test_sg' + ) + group_id = create_sg['GroupId'] + client.run_instances(ImageId=image_id, + MinCount=1, + MaxCount=1, + SecurityGroups=['test_sg']) + reservations = client.describe_instances(Filters=[ + {'Name': 'instance.group-id', 'Values': [group_id]} + ])['Reservations'] + reservations[0]['Instances'].should.have.length_of(1) @mock_ec2_deprecated def test_get_instances_filtering_by_tag(): @@ -814,7 +880,6 @@ def test_run_instance_with_nic_autocreated(): eni.private_ip_addresses.should.have.length_of(1) eni.private_ip_addresses[0].private_ip_address.should.equal(private_ip) - @mock_ec2_deprecated def test_run_instance_with_nic_preexisting(): conn = boto.connect_vpc('the_key', 'the_secret') @@ -943,11 +1008,9 @@ def test_ec2_classic_has_public_ip_address(): reservation = conn.run_instances('ami-1234abcd', key_name="keypair_name") instance = reservation.instances[0] instance.ip_address.should_not.equal(None) - instance.public_dns_name.should.contain(instance.ip_address) - + instance.public_dns_name.should.contain(instance.ip_address.replace('.', '-')) instance.private_ip_address.should_not.equal(None) - instance.private_dns_name.should.contain(instance.private_ip_address) - + instance.private_dns_name.should.contain(instance.private_ip_address.replace('.', '-')) @mock_ec2_deprecated def test_run_instance_with_keypair(): diff --git a/tests/test_ec2/test_key_pairs.py b/tests/test_ec2/test_key_pairs.py index ec979a871..0a7fb9f76 100644 --- a/tests/test_ec2/test_key_pairs.py +++ b/tests/test_ec2/test_key_pairs.py @@ -130,3 +130,22 @@ def test_key_pairs_import_exist(): cm.exception.code.should.equal('InvalidKeyPair.Duplicate') cm.exception.status.should.equal(400) cm.exception.request_id.should_not.be.none + + +@mock_ec2_deprecated +def test_key_pair_filters(): + conn = boto.connect_ec2('the_key', 'the_secret') + + _ = conn.create_key_pair('kpfltr1') + kp2 = conn.create_key_pair('kpfltr2') + kp3 = conn.create_key_pair('kpfltr3') + + kp_by_name = conn.get_all_key_pairs( + filters={'key-name': 'kpfltr2'}) + set([kp.name for kp in kp_by_name] + ).should.equal(set([kp2.name])) + + kp_by_name = conn.get_all_key_pairs( + filters={'fingerprint': kp3.fingerprint}) + set([kp.name for kp in kp_by_name] + ).should.equal(set([kp3.name])) diff --git a/tests/test_ec2/test_security_groups.py b/tests/test_ec2/test_security_groups.py index 21ecad11e..45e6e327d 100644 --- a/tests/test_ec2/test_security_groups.py +++ b/tests/test_ec2/test_security_groups.py @@ -348,6 +348,15 @@ def test_get_all_security_groups(): resp.should.have.length_of(1) resp[0].id.should.equal(sg1.id) + with assert_raises(EC2ResponseError) as cm: + conn.get_all_security_groups(groupnames=['does_not_exist']) + cm.exception.code.should.equal('InvalidGroup.NotFound') + cm.exception.status.should.equal(400) + cm.exception.request_id.should_not.be.none + + resp.should.have.length_of(1) + resp[0].id.should.equal(sg1.id) + resp = conn.get_all_security_groups(filters={'vpc-id': ['vpc-mjm05d27']}) resp.should.have.length_of(1) resp[0].id.should.equal(sg1.id) @@ -681,3 +690,9 @@ def test_get_all_security_groups_filter_with_same_vpc_id(): security_groups = conn.get_all_security_groups( group_ids=[security_group.id], filters={'vpc-id': [vpc_id]}) security_groups.should.have.length_of(1) + + with assert_raises(EC2ResponseError) as cm: + conn.get_all_security_groups(group_ids=['does_not_exist']) + cm.exception.code.should.equal('InvalidGroup.NotFound') + cm.exception.status.should.equal(400) + cm.exception.request_id.should_not.be.none diff --git a/tests/test_ec2/test_subnets.py b/tests/test_ec2/test_subnets.py index 38565a28f..99e6d45d8 100644 --- a/tests/test_ec2/test_subnets.py +++ b/tests/test_ec2/test_subnets.py @@ -158,6 +158,32 @@ def test_modify_subnet_attribute_validation(): SubnetId=subnet.id, MapPublicIpOnLaunch={'Value': 'invalid'}) +@mock_ec2_deprecated +def test_subnet_get_by_id(): + ec2 = boto.ec2.connect_to_region('us-west-1') + conn = boto.vpc.connect_to_region('us-west-1') + vpcA = conn.create_vpc("10.0.0.0/16") + subnetA = conn.create_subnet( + vpcA.id, "10.0.0.0/24", availability_zone='us-west-1a') + vpcB = conn.create_vpc("10.0.0.0/16") + subnetB1 = conn.create_subnet( + vpcB.id, "10.0.0.0/24", availability_zone='us-west-1a') + subnetB2 = conn.create_subnet( + vpcB.id, "10.0.1.0/24", availability_zone='us-west-1b') + + subnets_by_id = conn.get_all_subnets(subnet_ids=[subnetA.id, subnetB1.id]) + subnets_by_id.should.have.length_of(2) + subnets_by_id = tuple(map(lambda s: s.id, subnets_by_id)) + subnetA.id.should.be.within(subnets_by_id) + subnetB1.id.should.be.within(subnets_by_id) + + with assert_raises(EC2ResponseError) as cm: + conn.get_all_subnets(subnet_ids=['subnet-does_not_exist']) + cm.exception.code.should.equal('InvalidSubnetID.NotFound') + cm.exception.status.should.equal(400) + cm.exception.request_id.should_not.be.none + + @mock_ec2_deprecated def test_get_subnets_filtering(): ec2 = boto.ec2.connect_to_region('us-west-1') diff --git a/tests/test_ec2/test_vpcs.py b/tests/test_ec2/test_vpcs.py index 904603f6d..fc0a93cbb 100644 --- a/tests/test_ec2/test_vpcs.py +++ b/tests/test_ec2/test_vpcs.py @@ -113,6 +113,12 @@ def test_vpc_get_by_id(): vpc1.id.should.be.within(vpc_ids) vpc2.id.should.be.within(vpc_ids) + with assert_raises(EC2ResponseError) as cm: + conn.get_all_vpcs(vpc_ids=['vpc-does_not_exist']) + cm.exception.code.should.equal('InvalidVpcID.NotFound') + cm.exception.status.should.equal(400) + cm.exception.request_id.should_not.be.none + @mock_ec2_deprecated def test_vpc_get_by_cidr_block(): diff --git a/tests/test_ecr/test_ecr_boto3.py b/tests/test_ecr/test_ecr_boto3.py index 581906321..67d1a2cab 100644 --- a/tests/test_ecr/test_ecr_boto3.py +++ b/tests/test_ecr/test_ecr_boto3.py @@ -5,9 +5,11 @@ import json from datetime import datetime from random import random +import re import sure # noqa import boto3 +from botocore.exceptions import ClientError from dateutil.tz import tzlocal from moto import mock_ecr @@ -141,19 +143,6 @@ def test_describe_repositories_3(): response['repositories'][0]['repositoryUri'].should.equal(respository_uri) -@mock_ecr -def test_describe_repositories_4(): - client = boto3.client('ecr', region_name='us-east-1') - _ = client.create_repository( - repositoryName='test_repository1' - ) - _ = client.create_repository( - repositoryName='test_repository0' - ) - response = client.describe_repositories(repositoryNames=['not_a_valid_name']) - len(response['repositories']).should.equal(0) - - @mock_ecr def test_describe_repositories_with_image(): client = boto3.client('ecr', region_name='us-east-1') @@ -344,6 +333,54 @@ def test_describe_images_by_tag(): image_detail['imageDigest'].should.equal(put_response['imageId']['imageDigest']) +@mock_ecr +def test_describe_repository_that_doesnt_exist(): + client = boto3.client('ecr', region_name='us-east-1') + + error_msg = re.compile( + r".*The repository with name 'repo-that-doesnt-exist' does not exist in the registry with id '123'.*", + re.MULTILINE) + client.describe_repositories.when.called_with( + repositoryNames=['repo-that-doesnt-exist'], + registryId='123', + ).should.throw(ClientError, error_msg) + +@mock_ecr +def test_describe_image_that_doesnt_exist(): + client = boto3.client('ecr', region_name='us-east-1') + client.create_repository(repositoryName='test_repository') + + error_msg1 = re.compile( + r".*The image with imageId {imageDigest:'null', imageTag:'testtag'} does not exist within " + r"the repository with name 'test_repository' in the registry with id '123'.*", + re.MULTILINE) + + client.describe_images.when.called_with( + repositoryName='test_repository', imageIds=[{'imageTag': 'testtag'}], registryId='123', + ).should.throw(ClientError, error_msg1) + + error_msg2 = re.compile( + r".*The repository with name 'repo-that-doesnt-exist' does not exist in the registry with id '123'.*", + re.MULTILINE) + client.describe_images.when.called_with( + repositoryName='repo-that-doesnt-exist', imageIds=[{'imageTag': 'testtag'}], registryId='123', + ).should.throw(ClientError, error_msg2) + + +@mock_ecr +def test_delete_repository_that_doesnt_exist(): + client = boto3.client('ecr', region_name='us-east-1') + + error_msg = re.compile( + r".*The repository with name 'repo-that-doesnt-exist' does not exist in the registry with id '123'.*", + re.MULTILINE) + + client.delete_repository.when.called_with( + repositoryName='repo-that-doesnt-exist', + registryId='123').should.throw( + ClientError, error_msg) + + @mock_ecr def test_describe_images_by_digest(): client = boto3.client('ecr', region_name='us-east-1') diff --git a/tests/test_elbv2/test_elbv2.py b/tests/test_elbv2/test_elbv2.py index ece17571d..21799ddcf 100644 --- a/tests/test_elbv2/test_elbv2.py +++ b/tests/test_elbv2/test_elbv2.py @@ -251,6 +251,12 @@ def test_create_target_group_and_listeners(): UnhealthyThresholdCount=2, Matcher={'HttpCode': '200'}) target_group = response.get('TargetGroups')[0] + target_group_arn = target_group['TargetGroupArn'] + + # Add tags to the target group + conn.add_tags(ResourceArns=[target_group_arn], Tags=[{'Key': 'target', 'Value': 'group'}]) + conn.describe_tags(ResourceArns=[target_group_arn])['TagDescriptions'][0]['Tags'].should.equal( + [{'Key': 'target', 'Value': 'group'}]) # Check it's in the describe_target_groups response response = conn.describe_target_groups() @@ -328,6 +334,65 @@ def test_create_target_group_and_listeners(): response.get('TargetGroups').should.have.length_of(0) +@mock_elbv2 +@mock_ec2 +def test_create_invalid_target_group(): + conn = boto3.client('elbv2', region_name='us-east-1') + ec2 = boto3.resource('ec2', region_name='us-east-1') + + vpc = ec2.create_vpc(CidrBlock='172.28.7.0/24', InstanceTenancy='default') + + # Fail to create target group with name which length is 33 + long_name = 'A' * 33 + with assert_raises(ClientError): + conn.create_target_group( + Name=long_name, + Protocol='HTTP', + Port=8080, + VpcId=vpc.id, + HealthCheckProtocol='HTTP', + HealthCheckPort='8080', + HealthCheckPath='/', + HealthCheckIntervalSeconds=5, + HealthCheckTimeoutSeconds=5, + HealthyThresholdCount=5, + UnhealthyThresholdCount=2, + Matcher={'HttpCode': '200'}) + + invalid_names = ['-name', 'name-', '-name-', 'example.com', 'test@test', 'Na--me'] + for name in invalid_names: + with assert_raises(ClientError): + conn.create_target_group( + Name=name, + Protocol='HTTP', + Port=8080, + VpcId=vpc.id, + HealthCheckProtocol='HTTP', + HealthCheckPort='8080', + HealthCheckPath='/', + HealthCheckIntervalSeconds=5, + HealthCheckTimeoutSeconds=5, + HealthyThresholdCount=5, + UnhealthyThresholdCount=2, + Matcher={'HttpCode': '200'}) + + valid_names = ['name', 'Name', '000'] + for name in valid_names: + conn.create_target_group( + Name=name, + Protocol='HTTP', + Port=8080, + VpcId=vpc.id, + HealthCheckProtocol='HTTP', + HealthCheckPort='8080', + HealthCheckPath='/', + HealthCheckIntervalSeconds=5, + HealthCheckTimeoutSeconds=5, + HealthyThresholdCount=5, + UnhealthyThresholdCount=2, + Matcher={'HttpCode': '200'}) + + @mock_elbv2 @mock_ec2 def test_describe_paginated_balancers(): @@ -485,6 +550,12 @@ def test_target_group_attributes(): response.get('TargetGroups').should.have.length_of(1) target_group_arn = target_group['TargetGroupArn'] + # check if Names filter works + response = conn.describe_target_groups(Names=[]) + response = conn.describe_target_groups(Names=['a-target']) + response.get('TargetGroups').should.have.length_of(1) + target_group_arn = target_group['TargetGroupArn'] + # The attributes should start with the two defaults response = conn.describe_target_group_attributes(TargetGroupArn=target_group_arn) response['Attributes'].should.have.length_of(2) @@ -518,3 +589,321 @@ def test_target_group_attributes(): attributes = {attr['Key']: attr['Value'] for attr in response['Attributes']} attributes['stickiness.type'].should.equal('lb_cookie') attributes['stickiness.enabled'].should.equal('true') + + +@mock_elbv2 +@mock_ec2 +def test_handle_listener_rules(): + conn = boto3.client('elbv2', region_name='us-east-1') + ec2 = boto3.resource('ec2', region_name='us-east-1') + + security_group = ec2.create_security_group(GroupName='a-security-group', Description='First One') + vpc = ec2.create_vpc(CidrBlock='172.28.7.0/24', InstanceTenancy='default') + subnet1 = ec2.create_subnet(VpcId=vpc.id, CidrBlock='172.28.7.192/26', AvailabilityZone='us-east-1a') + subnet2 = ec2.create_subnet(VpcId=vpc.id, CidrBlock='172.28.7.192/26', AvailabilityZone='us-east-1b') + + response = conn.create_load_balancer( + Name='my-lb', + Subnets=[subnet1.id, subnet2.id], + SecurityGroups=[security_group.id], + Scheme='internal', + Tags=[{'Key': 'key_name', 'Value': 'a_value'}]) + + load_balancer_arn = response.get('LoadBalancers')[0].get('LoadBalancerArn') + + response = conn.create_target_group( + Name='a-target', + Protocol='HTTP', + Port=8080, + VpcId=vpc.id, + HealthCheckProtocol='HTTP', + HealthCheckPort='8080', + HealthCheckPath='/', + HealthCheckIntervalSeconds=5, + HealthCheckTimeoutSeconds=5, + HealthyThresholdCount=5, + UnhealthyThresholdCount=2, + Matcher={'HttpCode': '200'}) + target_group = response.get('TargetGroups')[0] + + # Plain HTTP listener + response = conn.create_listener( + LoadBalancerArn=load_balancer_arn, + Protocol='HTTP', + Port=80, + DefaultActions=[{'Type': 'forward', 'TargetGroupArn': target_group.get('TargetGroupArn')}]) + listener = response.get('Listeners')[0] + listener.get('Port').should.equal(80) + listener.get('Protocol').should.equal('HTTP') + listener.get('DefaultActions').should.equal([{ + 'TargetGroupArn': target_group.get('TargetGroupArn'), + 'Type': 'forward'}]) + http_listener_arn = listener.get('ListenerArn') + + # create first rule + priority = 100 + host = 'xxx.example.com' + path_pattern = 'foobar' + created_rule = conn.create_rule( + ListenerArn=http_listener_arn, + Priority=priority, + Conditions=[{ + 'Field': 'host-header', + 'Values': [ host ] + }, + { + 'Field': 'path-pattern', + 'Values': [ path_pattern ] + }], + Actions=[{ + 'TargetGroupArn': target_group.get('TargetGroupArn'), + 'Type': 'forward' + }] + )['Rules'][0] + created_rule['Priority'].should.equal('100') + + # check if rules is sorted by priority + priority = 50 + host = 'yyy.example.com' + path_pattern = 'foobar' + rules = conn.create_rule( + ListenerArn=http_listener_arn, + Priority=priority, + Conditions=[{ + 'Field': 'host-header', + 'Values': [ host ] + }, + { + 'Field': 'path-pattern', + 'Values': [ path_pattern ] + }], + Actions=[{ + 'TargetGroupArn': target_group.get('TargetGroupArn'), + 'Type': 'forward' + }] + ) + + # test for PriorityInUse + host2 = 'yyy.example.com' + with assert_raises(ClientError): + r = conn.create_rule( + ListenerArn=http_listener_arn, + Priority=priority, + Conditions=[{ + 'Field': 'host-header', + 'Values': [ host ] + }, + { + 'Field': 'path-pattern', + 'Values': [ path_pattern ] + }], + Actions=[{ + 'TargetGroupArn': target_group.get('TargetGroupArn'), + 'Type': 'forward' + }] + ) + + + # test for describe listeners + obtained_rules = conn.describe_rules(ListenerArn=http_listener_arn) + len(obtained_rules['Rules']).should.equal(3) + priorities = [rule['Priority'] for rule in obtained_rules['Rules']] + priorities.should.equal(['50', '100', 'default']) + + first_rule = obtained_rules['Rules'][0] + second_rule = obtained_rules['Rules'][1] + obtained_rules = conn.describe_rules(RuleArns=[first_rule['RuleArn']]) + obtained_rules['Rules'].should.equal([first_rule]) + + # test for pagination + obtained_rules = conn.describe_rules(ListenerArn=http_listener_arn, PageSize=1) + len(obtained_rules['Rules']).should.equal(1) + obtained_rules.should.have.key('NextMarker') + next_marker = obtained_rules['NextMarker'] + + following_rules = conn.describe_rules(ListenerArn=http_listener_arn, PageSize=1, Marker=next_marker) + len(following_rules['Rules']).should.equal(1) + following_rules.should.have.key('NextMarker') + following_rules['Rules'][0]['RuleArn'].should_not.equal(obtained_rules['Rules'][0]['RuleArn']) + + # test for invalid describe rule request + with assert_raises(ClientError): + conn.describe_rules() + with assert_raises(ClientError): + conn.describe_rules(RuleArns=[]) + with assert_raises(ClientError): + conn.describe_rules( + ListenerArn=http_listener_arn, + RuleArns=[first_rule['RuleArn']] + ) + + # modify rule partially + new_host = 'new.example.com' + new_path_pattern = 'new_path' + modified_rule = conn.modify_rule( + RuleArn=first_rule['RuleArn'], + Conditions=[{ + 'Field': 'host-header', + 'Values': [ new_host ] + }, + { + 'Field': 'path-pattern', + 'Values': [ new_path_pattern ] + }] + )['Rules'][0] + + rules = conn.describe_rules(ListenerArn=http_listener_arn) + obtained_rule = rules['Rules'][0] + modified_rule.should.equal(obtained_rule) + obtained_rule['Conditions'][0]['Values'][0].should.equal(new_host) + obtained_rule['Conditions'][1]['Values'][0].should.equal(new_path_pattern) + obtained_rule['Actions'][0]['TargetGroupArn'].should.equal(target_group.get('TargetGroupArn')) + + # modify priority + conn.set_rule_priorities( + RulePriorities=[ + {'RuleArn': first_rule['RuleArn'], 'Priority': int(first_rule['Priority']) - 1} + ] + ) + with assert_raises(ClientError): + conn.set_rule_priorities( + RulePriorities=[ + {'RuleArn': first_rule['RuleArn'], 'Priority': 999}, + {'RuleArn': second_rule['RuleArn'], 'Priority': 999} + ] + ) + + # delete + arn = first_rule['RuleArn'] + conn.delete_rule(RuleArn=arn) + rules = conn.describe_rules(ListenerArn=http_listener_arn)['Rules'] + len(rules).should.equal(2) + + # test for invalid action type + safe_priority = 2 + with assert_raises(ClientError): + r = conn.create_rule( + ListenerArn=http_listener_arn, + Priority=safe_priority, + Conditions=[{ + 'Field': 'host-header', + 'Values': [ host ] + }, + { + 'Field': 'path-pattern', + 'Values': [ path_pattern ] + }], + Actions=[{ + 'TargetGroupArn': target_group.get('TargetGroupArn'), + 'Type': 'forward2' + }] + ) + + # test for invalid action type + safe_priority = 2 + invalid_target_group_arn = target_group.get('TargetGroupArn') + 'x' + with assert_raises(ClientError): + r = conn.create_rule( + ListenerArn=http_listener_arn, + Priority=safe_priority, + Conditions=[{ + 'Field': 'host-header', + 'Values': [ host ] + }, + { + 'Field': 'path-pattern', + 'Values': [ path_pattern ] + }], + Actions=[{ + 'TargetGroupArn': invalid_target_group_arn, + 'Type': 'forward' + }] + ) + + # test for invalid condition field_name + safe_priority = 2 + with assert_raises(ClientError): + r = conn.create_rule( + ListenerArn=http_listener_arn, + Priority=safe_priority, + Conditions=[{ + 'Field': 'xxxxxxx', + 'Values': [ host ] + }], + Actions=[{ + 'TargetGroupArn': target_group.get('TargetGroupArn'), + 'Type': 'forward' + }] + ) + + # test for emptry condition value + safe_priority = 2 + with assert_raises(ClientError): + r = conn.create_rule( + ListenerArn=http_listener_arn, + Priority=safe_priority, + Conditions=[{ + 'Field': 'host-header', + 'Values': [] + }], + Actions=[{ + 'TargetGroupArn': target_group.get('TargetGroupArn'), + 'Type': 'forward' + }] + ) + + # test for multiple condition value + safe_priority = 2 + with assert_raises(ClientError): + r = conn.create_rule( + ListenerArn=http_listener_arn, + Priority=safe_priority, + Conditions=[{ + 'Field': 'host-header', + 'Values': [host, host] + }], + Actions=[{ + 'TargetGroupArn': target_group.get('TargetGroupArn'), + 'Type': 'forward' + }] + ) + + +@mock_elbv2 +@mock_ec2 +def test_describe_invalid_target_group(): + conn = boto3.client('elbv2', region_name='us-east-1') + ec2 = boto3.resource('ec2', region_name='us-east-1') + + security_group = ec2.create_security_group(GroupName='a-security-group', Description='First One') + vpc = ec2.create_vpc(CidrBlock='172.28.7.0/24', InstanceTenancy='default') + subnet1 = ec2.create_subnet(VpcId=vpc.id, CidrBlock='172.28.7.192/26', AvailabilityZone='us-east-1a') + subnet2 = ec2.create_subnet(VpcId=vpc.id, CidrBlock='172.28.7.192/26', AvailabilityZone='us-east-1b') + + response = conn.create_load_balancer( + Name='my-lb', + Subnets=[subnet1.id, subnet2.id], + SecurityGroups=[security_group.id], + Scheme='internal', + Tags=[{'Key': 'key_name', 'Value': 'a_value'}]) + + load_balancer_arn = response.get('LoadBalancers')[0].get('LoadBalancerArn') + + response = conn.create_target_group( + Name='a-target', + Protocol='HTTP', + Port=8080, + VpcId=vpc.id, + HealthCheckProtocol='HTTP', + HealthCheckPort='8080', + HealthCheckPath='/', + HealthCheckIntervalSeconds=5, + HealthCheckTimeoutSeconds=5, + HealthyThresholdCount=5, + UnhealthyThresholdCount=2, + Matcher={'HttpCode': '200'}) + target_group = response.get('TargetGroups')[0] + + # Check error raises correctly + with assert_raises(ClientError): + conn.describe_target_groups(Names=['invalid']) diff --git a/tests/test_iam/test_iam.py b/tests/test_iam/test_iam.py index b5968f722..a80768101 100644 --- a/tests/test_iam/test_iam.py +++ b/tests/test_iam/test_iam.py @@ -213,8 +213,21 @@ def test_list_role_policies(): conn.create_role("my-role") conn.put_role_policy("my-role", "test policy", "my policy") role = conn.list_role_policies("my-role") + role.policy_names.should.have.length_of(1) role.policy_names[0].should.equal("test policy") + conn.put_role_policy("my-role", "test policy 2", "another policy") + role = conn.list_role_policies("my-role") + role.policy_names.should.have.length_of(2) + + conn.delete_role_policy("my-role", "test policy") + role = conn.list_role_policies("my-role") + role.policy_names.should.have.length_of(1) + role.policy_names[0].should.equal("test policy 2") + + with assert_raises(BotoServerError): + conn.delete_role_policy("my-role", "test policy") + @mock_iam_deprecated() def test_put_role_policy(): @@ -512,8 +525,14 @@ def test_managed_policy(): path='/mypolicy/', description='my user managed policy') - aws_policies = conn.list_policies(scope='AWS')['list_policies_response'][ - 'list_policies_result']['policies'] + marker = 0 + aws_policies = [] + while marker is not None: + response = conn.list_policies(scope='AWS', marker=marker)[ + 'list_policies_response']['list_policies_result'] + for policy in response['policies']: + aws_policies.append(policy) + marker = response.get('marker') set(p.name for p in aws_managed_policies).should.equal( set(p['policy_name'] for p in aws_policies)) @@ -522,8 +541,14 @@ def test_managed_policy(): set(['UserManagedPolicy']).should.equal( set(p['policy_name'] for p in user_policies)) - all_policies = conn.list_policies()['list_policies_response'][ - 'list_policies_result']['policies'] + marker = 0 + all_policies = [] + while marker is not None: + response = conn.list_policies(marker=marker)[ + 'list_policies_response']['list_policies_result'] + for policy in response['policies']: + all_policies.append(policy) + marker = response.get('marker') set(p['policy_name'] for p in aws_policies + user_policies).should.equal(set(p['policy_name'] for p in all_policies)) @@ -548,6 +573,31 @@ def test_managed_policy(): resp['list_attached_role_policies_response']['list_attached_role_policies_result'][ 'attached_policies'].should.have.length_of(2) + conn.detach_role_policy( + "arn:aws:iam::aws:policy/service-role/AmazonElasticMapReduceRole", + role_name) + rows = conn.list_policies(only_attached=True)['list_policies_response'][ + 'list_policies_result']['policies'] + rows.should.have.length_of(1) + for x in rows: + int(x['attachment_count']).should.be.greater_than(0) + + # boto has not implemented this end point but accessible this way + resp = conn.get_response('ListAttachedRolePolicies', + {'RoleName': role_name}, + list_marker='AttachedPolicies') + resp['list_attached_role_policies_response']['list_attached_role_policies_result'][ + 'attached_policies'].should.have.length_of(1) + + with assert_raises(BotoServerError): + conn.detach_role_policy( + "arn:aws:iam::aws:policy/service-role/AmazonElasticMapReduceRole", + role_name) + + with assert_raises(BotoServerError): + conn.detach_role_policy( + "arn:aws:iam::aws:policy/Nonexistent", role_name) + @mock_iam def test_boto3_create_login_profile(): @@ -561,3 +611,30 @@ def test_boto3_create_login_profile(): with assert_raises(ClientError): conn.create_login_profile(UserName='my-user', Password='Password') + + +@mock_iam() +def test_attach_detach_user_policy(): + iam = boto3.resource('iam', region_name='us-east-1') + client = boto3.client('iam', region_name='us-east-1') + + user = iam.create_user(UserName='test-user') + + policy_name = 'UserAttachedPolicy' + policy = iam.create_policy(PolicyName=policy_name, + PolicyDocument='{"mypolicy": "test"}', + Path='/mypolicy/', + Description='my user attached policy') + + client.attach_user_policy(UserName=user.name, PolicyArn=policy.arn) + + resp = client.list_attached_user_policies(UserName=user.name) + resp['AttachedPolicies'].should.have.length_of(1) + attached_policy = resp['AttachedPolicies'][0] + attached_policy['PolicyArn'].should.equal(policy.arn) + attached_policy['PolicyName'].should.equal(policy_name) + + client.detach_user_policy(UserName=user.name, PolicyArn=policy.arn) + + resp = client.list_attached_user_policies(UserName=user.name) + resp['AttachedPolicies'].should.have.length_of(0) diff --git a/tests/test_iam/test_iam_account_aliases.py b/tests/test_iam/test_iam_account_aliases.py new file mode 100644 index 000000000..3d927038d --- /dev/null +++ b/tests/test_iam/test_iam_account_aliases.py @@ -0,0 +1,20 @@ +import boto3 +import sure # noqa +from moto import mock_iam + + +@mock_iam() +def test_account_aliases(): + client = boto3.client('iam', region_name='us-east-1') + + alias = 'my-account-name' + aliases = client.list_account_aliases() + aliases.should.have.key('AccountAliases').which.should.equal([]) + + client.create_account_alias(AccountAlias=alias) + aliases = client.list_account_aliases() + aliases.should.have.key('AccountAliases').which.should.equal([alias]) + + client.delete_account_alias(AccountAlias=alias) + aliases = client.list_account_aliases() + aliases.should.have.key('AccountAliases').which.should.equal([]) diff --git a/tests/test_iam/test_iam_groups.py b/tests/test_iam/test_iam_groups.py index 9d5095884..49c7987f6 100644 --- a/tests/test_iam/test_iam_groups.py +++ b/tests/test_iam/test_iam_groups.py @@ -82,6 +82,26 @@ def test_put_group_policy(): conn.put_group_policy('my-group', 'my-policy', '{"some": "json"}') +@mock_iam +def test_attach_group_policies(): + conn = boto3.client('iam', region_name='us-east-1') + conn.create_group(GroupName='my-group') + conn.list_attached_group_policies(GroupName='my-group')['AttachedPolicies'].should.be.empty + policy_arn = 'arn:aws:iam::aws:policy/service-role/AmazonElasticMapReduceforEC2Role' + conn.list_attached_group_policies(GroupName='my-group')['AttachedPolicies'].should.be.empty + conn.attach_group_policy(GroupName='my-group', PolicyArn=policy_arn) + conn.list_attached_group_policies(GroupName='my-group')['AttachedPolicies'].should.equal( + [ + { + 'PolicyName': 'AmazonElasticMapReduceforEC2Role', + 'PolicyArn': policy_arn, + } + ]) + + conn.detach_group_policy(GroupName='my-group', PolicyArn=policy_arn) + conn.list_attached_group_policies(GroupName='my-group')['AttachedPolicies'].should.be.empty + + @mock_iam_deprecated() def test_get_group_policy(): conn = boto.connect_iam() @@ -90,7 +110,8 @@ def test_get_group_policy(): conn.get_group_policy('my-group', 'my-policy') conn.put_group_policy('my-group', 'my-policy', '{"some": "json"}') - policy = conn.get_group_policy('my-group', 'my-policy') + conn.get_group_policy('my-group', 'my-policy') + @mock_iam_deprecated() def test_get_all_group_policies(): @@ -107,6 +128,6 @@ def test_get_all_group_policies(): def test_list_group_policies(): conn = boto3.client('iam', region_name='us-east-1') conn.create_group(GroupName='my-group') - policies = conn.list_group_policies(GroupName='my-group')['PolicyNames'].should.be.empty + conn.list_group_policies(GroupName='my-group')['PolicyNames'].should.be.empty conn.put_group_policy(GroupName='my-group', PolicyName='my-policy', PolicyDocument='{"some": "json"}') - policies = conn.list_group_policies(GroupName='my-group')['PolicyNames'].should.equal(['my-policy']) + conn.list_group_policies(GroupName='my-group')['PolicyNames'].should.equal(['my-policy']) diff --git a/tests/test_logs/test_logs.py b/tests/test_logs/test_logs.py new file mode 100644 index 000000000..392b3f7e9 --- /dev/null +++ b/tests/test_logs/test_logs.py @@ -0,0 +1,14 @@ +import boto3 +import sure # noqa + +from moto import mock_logs, settings + +_logs_region = 'us-east-1' if settings.TEST_SERVER_MODE else 'us-west-2' + + +@mock_logs +def test_log_group_create(): + conn = boto3.client('logs', 'us-west-2') + log_group_name = 'dummy' + response = conn.create_log_group(logGroupName=log_group_name) + response = conn.delete_log_group(logGroupName=log_group_name) diff --git a/tests/test_opsworks/test_layers.py b/tests/test_opsworks/test_layers.py index 31fdeae8c..03224feb0 100644 --- a/tests/test_opsworks/test_layers.py +++ b/tests/test_opsworks/test_layers.py @@ -27,6 +27,22 @@ def test_create_layer_response(): response.should.contain("LayerId") + second_stack_id = client.create_stack( + Name="test_stack_2", + Region="us-east-1", + ServiceRoleArn="service_arn", + DefaultInstanceProfileArn="profile_arn" + )['StackId'] + + response = client.create_layer( + StackId=second_stack_id, + Type="custom", + Name="TestLayer", + Shortname="TestLayerShortName" + ) + + response.should.contain("LayerId") + # ClientError client.create_layer.when.called_with( StackId=stack_id, diff --git a/tests/test_polly/test_polly.py b/tests/test_polly/test_polly.py new file mode 100644 index 000000000..c5c864835 --- /dev/null +++ b/tests/test_polly/test_polly.py @@ -0,0 +1,275 @@ +from __future__ import unicode_literals + +from botocore.exceptions import ClientError +import boto3 +import sure # noqa +from nose.tools import assert_raises +from moto import mock_polly + +# Polly only available in a few regions +DEFAULT_REGION = 'eu-west-1' + +LEXICON_XML = """ + + + W3C + World Wide Web Consortium + +""" + + +@mock_polly +def test_describe_voices(): + client = boto3.client('polly', region_name=DEFAULT_REGION) + + resp = client.describe_voices() + len(resp['Voices']).should.be.greater_than(1) + + resp = client.describe_voices(LanguageCode='en-GB') + len(resp['Voices']).should.equal(3) + + try: + client.describe_voices(LanguageCode='SOME_LANGUAGE') + except ClientError as err: + err.response['Error']['Code'].should.equal('400') + else: + raise RuntimeError('Should of raised an exception') + + +@mock_polly +def test_put_list_lexicon(): + client = boto3.client('polly', region_name=DEFAULT_REGION) + + # Return nothing + client.put_lexicon( + Name='test', + Content=LEXICON_XML + ) + + resp = client.list_lexicons() + len(resp['Lexicons']).should.equal(1) + + +@mock_polly +def test_put_get_lexicon(): + client = boto3.client('polly', region_name=DEFAULT_REGION) + + # Return nothing + client.put_lexicon( + Name='test', + Content=LEXICON_XML + ) + + resp = client.get_lexicon(Name='test') + resp.should.contain('Lexicon') + resp.should.contain('LexiconAttributes') + + +@mock_polly +def test_put_lexicon_bad_name(): + client = boto3.client('polly', region_name=DEFAULT_REGION) + + try: + client.put_lexicon( + Name='test-invalid', + Content=LEXICON_XML + ) + except ClientError as err: + err.response['Error']['Code'].should.equal('InvalidParameterValue') + else: + raise RuntimeError('Should of raised an exception') + + +@mock_polly +def test_synthesize_speech(): + client = boto3.client('polly', region_name=DEFAULT_REGION) + + # Return nothing + client.put_lexicon( + Name='test', + Content=LEXICON_XML + ) + + tests = ( + ('pcm', 'audio/pcm'), + ('mp3', 'audio/mpeg'), + ('ogg_vorbis', 'audio/ogg'), + ) + for output_format, content_type in tests: + resp = client.synthesize_speech( + LexiconNames=['test'], + OutputFormat=output_format, + SampleRate='16000', + Text='test1234', + TextType='text', + VoiceId='Astrid' + ) + resp['ContentType'].should.equal(content_type) + + +@mock_polly +def test_synthesize_speech_bad_lexicon(): + client = boto3.client('polly', region_name=DEFAULT_REGION) + client.put_lexicon(Name='test', Content=LEXICON_XML) + + try: + client.synthesize_speech( + LexiconNames=['test2'], + OutputFormat='pcm', + SampleRate='16000', + Text='test1234', + TextType='text', + VoiceId='Astrid' + ) + except ClientError as err: + err.response['Error']['Code'].should.equal('LexiconNotFoundException') + else: + raise RuntimeError('Should of raised LexiconNotFoundException') + + +@mock_polly +def test_synthesize_speech_bad_output_format(): + client = boto3.client('polly', region_name=DEFAULT_REGION) + client.put_lexicon(Name='test', Content=LEXICON_XML) + + try: + client.synthesize_speech( + LexiconNames=['test'], + OutputFormat='invalid', + SampleRate='16000', + Text='test1234', + TextType='text', + VoiceId='Astrid' + ) + except ClientError as err: + err.response['Error']['Code'].should.equal('InvalidParameterValue') + else: + raise RuntimeError('Should of raised ') + + +@mock_polly +def test_synthesize_speech_bad_sample_rate(): + client = boto3.client('polly', region_name=DEFAULT_REGION) + client.put_lexicon(Name='test', Content=LEXICON_XML) + + try: + client.synthesize_speech( + LexiconNames=['test'], + OutputFormat='pcm', + SampleRate='18000', + Text='test1234', + TextType='text', + VoiceId='Astrid' + ) + except ClientError as err: + err.response['Error']['Code'].should.equal('InvalidSampleRateException') + else: + raise RuntimeError('Should of raised ') + + +@mock_polly +def test_synthesize_speech_bad_text_type(): + client = boto3.client('polly', region_name=DEFAULT_REGION) + client.put_lexicon(Name='test', Content=LEXICON_XML) + + try: + client.synthesize_speech( + LexiconNames=['test'], + OutputFormat='pcm', + SampleRate='16000', + Text='test1234', + TextType='invalid', + VoiceId='Astrid' + ) + except ClientError as err: + err.response['Error']['Code'].should.equal('InvalidParameterValue') + else: + raise RuntimeError('Should of raised ') + + +@mock_polly +def test_synthesize_speech_bad_voice_id(): + client = boto3.client('polly', region_name=DEFAULT_REGION) + client.put_lexicon(Name='test', Content=LEXICON_XML) + + try: + client.synthesize_speech( + LexiconNames=['test'], + OutputFormat='pcm', + SampleRate='16000', + Text='test1234', + TextType='text', + VoiceId='Luke' + ) + except ClientError as err: + err.response['Error']['Code'].should.equal('InvalidParameterValue') + else: + raise RuntimeError('Should of raised ') + + +@mock_polly +def test_synthesize_speech_text_too_long(): + client = boto3.client('polly', region_name=DEFAULT_REGION) + client.put_lexicon(Name='test', Content=LEXICON_XML) + + try: + client.synthesize_speech( + LexiconNames=['test'], + OutputFormat='pcm', + SampleRate='16000', + Text='test1234'*376, # = 3008 characters + TextType='text', + VoiceId='Astrid' + ) + except ClientError as err: + err.response['Error']['Code'].should.equal('TextLengthExceededException') + else: + raise RuntimeError('Should of raised ') + + +@mock_polly +def test_synthesize_speech_bad_speech_marks1(): + client = boto3.client('polly', region_name=DEFAULT_REGION) + client.put_lexicon(Name='test', Content=LEXICON_XML) + + try: + client.synthesize_speech( + LexiconNames=['test'], + OutputFormat='pcm', + SampleRate='16000', + Text='test1234', + TextType='text', + SpeechMarkTypes=['word'], + VoiceId='Astrid' + ) + except ClientError as err: + err.response['Error']['Code'].should.equal('MarksNotSupportedForFormatException') + else: + raise RuntimeError('Should of raised ') + + +@mock_polly +def test_synthesize_speech_bad_speech_marks2(): + client = boto3.client('polly', region_name=DEFAULT_REGION) + client.put_lexicon(Name='test', Content=LEXICON_XML) + + try: + client.synthesize_speech( + LexiconNames=['test'], + OutputFormat='pcm', + SampleRate='16000', + Text='test1234', + TextType='ssml', + SpeechMarkTypes=['word'], + VoiceId='Astrid' + ) + except ClientError as err: + err.response['Error']['Code'].should.equal('MarksNotSupportedForFormatException') + else: + raise RuntimeError('Should of raised ') diff --git a/tests/test_polly/test_server.py b/tests/test_polly/test_server.py new file mode 100644 index 000000000..3ae7f2254 --- /dev/null +++ b/tests/test_polly/test_server.py @@ -0,0 +1,19 @@ +from __future__ import unicode_literals + +import sure # noqa + +import moto.server as server +from moto import mock_polly + +''' +Test the different server responses +''' + + +@mock_polly +def test_polly_list(): + backend = server.create_backend_app("polly") + test_client = backend.test_client() + + res = test_client.get('/v1/lexicons') + res.status_code.should.equal(200) diff --git a/tests/test_rds2/test_rds2.py b/tests/test_rds2/test_rds2.py index a50f99868..4ab7dbc60 100644 --- a/tests/test_rds2/test_rds2.py +++ b/tests/test_rds2/test_rds2.py @@ -19,9 +19,6 @@ def test_create_database(): MasterUserPassword='hunter2', Port=1234, DBSecurityGroups=["my_sg"]) - database['DBInstance']['DBInstanceStatus'].should.equal('available') - database['DBInstance']['DBName'].should.equal('staging-postgres') - database['DBInstance']['DBInstanceIdentifier'].should.equal("db-master-1") database['DBInstance']['AllocatedStorage'].should.equal(10) database['DBInstance']['DBInstanceClass'].should.equal("db.m1.small") database['DBInstance']['LicenseModel'].should.equal("license-included") @@ -30,6 +27,122 @@ def test_create_database(): 'DBSecurityGroupName'].should.equal('my_sg') database['DBInstance']['DBInstanceArn'].should.equal( 'arn:aws:rds:us-west-2:1234567890:db:db-master-1') + database['DBInstance']['DBInstanceStatus'].should.equal('available') + database['DBInstance']['DBName'].should.equal('staging-postgres') + database['DBInstance']['DBInstanceIdentifier'].should.equal("db-master-1") + + +@mock_rds2 +def test_stop_database(): + conn = boto3.client('rds', region_name='us-west-2') + database = conn.create_db_instance(DBInstanceIdentifier='db-master-1', + AllocatedStorage=10, + Engine='postgres', + DBName='staging-postgres', + DBInstanceClass='db.m1.small', + LicenseModel='license-included', + MasterUsername='root', + MasterUserPassword='hunter2', + Port=1234, + DBSecurityGroups=["my_sg"]) + mydb = conn.describe_db_instances(DBInstanceIdentifier=database['DBInstance']['DBInstanceIdentifier'])['DBInstances'][0] + mydb['DBInstanceStatus'].should.equal('available') + # test stopping database should shutdown + response = conn.stop_db_instance(DBInstanceIdentifier=mydb['DBInstanceIdentifier']) + response['ResponseMetadata']['HTTPStatusCode'].should.equal(200) + response['DBInstance']['DBInstanceStatus'].should.equal('shutdown') + # test rdsclient error when trying to stop an already stopped database + conn.stop_db_instance.when.called_with(DBInstanceIdentifier=mydb['DBInstanceIdentifier']).should.throw(ClientError) + # test stopping a stopped database with snapshot should error and no snapshot should exist for that call + conn.stop_db_instance.when.called_with(DBInstanceIdentifier=mydb['DBInstanceIdentifier'], DBSnapshotIdentifier='rocky4570-rds-snap').should.throw(ClientError) + response = conn.describe_db_snapshots() + response['DBSnapshots'].should.equal([]) + + +@mock_rds2 +def test_start_database(): + conn = boto3.client('rds', region_name='us-west-2') + database = conn.create_db_instance(DBInstanceIdentifier='db-master-1', + AllocatedStorage=10, + Engine='postgres', + DBName='staging-postgres', + DBInstanceClass='db.m1.small', + LicenseModel='license-included', + MasterUsername='root', + MasterUserPassword='hunter2', + Port=1234, + DBSecurityGroups=["my_sg"]) + mydb = conn.describe_db_instances(DBInstanceIdentifier=database['DBInstance']['DBInstanceIdentifier'])['DBInstances'][0] + mydb['DBInstanceStatus'].should.equal('available') + # test starting an already started database should error + conn.start_db_instance.when.called_with(DBInstanceIdentifier=mydb['DBInstanceIdentifier']).should.throw(ClientError) + # stop and test start - should go from shutdown to available, create snapshot and check snapshot + response = conn.stop_db_instance(DBInstanceIdentifier=mydb['DBInstanceIdentifier'], DBSnapshotIdentifier='rocky4570-rds-snap') + response['ResponseMetadata']['HTTPStatusCode'].should.equal(200) + response['DBInstance']['DBInstanceStatus'].should.equal('shutdown') + response = conn.describe_db_snapshots() + response['DBSnapshots'][0]['DBSnapshotIdentifier'].should.equal('rocky4570-rds-snap') + response = conn.start_db_instance(DBInstanceIdentifier=mydb['DBInstanceIdentifier']) + response['ResponseMetadata']['HTTPStatusCode'].should.equal(200) + response['DBInstance']['DBInstanceStatus'].should.equal('available') + # starting database should not remove snapshot + response = conn.describe_db_snapshots() + response['DBSnapshots'][0]['DBSnapshotIdentifier'].should.equal('rocky4570-rds-snap') + # test stopping database, create snapshot with existing snapshot already created should throw error + conn.stop_db_instance.when.called_with(DBInstanceIdentifier=mydb['DBInstanceIdentifier'], DBSnapshotIdentifier='rocky4570-rds-snap').should.throw(ClientError) + # test stopping database not invoking snapshot should succeed. + response = conn.stop_db_instance(DBInstanceIdentifier=mydb['DBInstanceIdentifier']) + response['ResponseMetadata']['HTTPStatusCode'].should.equal(200) + response['DBInstance']['DBInstanceStatus'].should.equal('shutdown') + + +@mock_rds2 +def test_fail_to_stop_multi_az(): + conn = boto3.client('rds', region_name='us-west-2') + database = conn.create_db_instance(DBInstanceIdentifier='db-master-1', + AllocatedStorage=10, + Engine='postgres', + DBName='staging-postgres', + DBInstanceClass='db.m1.small', + LicenseModel='license-included', + MasterUsername='root', + MasterUserPassword='hunter2', + Port=1234, + DBSecurityGroups=["my_sg"], + MultiAZ=True) + + mydb = conn.describe_db_instances(DBInstanceIdentifier=database['DBInstance']['DBInstanceIdentifier'])['DBInstances'][0] + mydb['DBInstanceStatus'].should.equal('available') + # multi-az databases arent allowed to be shutdown at this time. + conn.stop_db_instance.when.called_with(DBInstanceIdentifier=mydb['DBInstanceIdentifier']).should.throw(ClientError) + # multi-az databases arent allowed to be started up at this time. + conn.start_db_instance.when.called_with(DBInstanceIdentifier=mydb['DBInstanceIdentifier']).should.throw(ClientError) + + +@mock_rds2 +def test_fail_to_stop_readreplica(): + conn = boto3.client('rds', region_name='us-west-2') + database = conn.create_db_instance(DBInstanceIdentifier='db-master-1', + AllocatedStorage=10, + Engine='postgres', + DBName='staging-postgres', + DBInstanceClass='db.m1.small', + LicenseModel='license-included', + MasterUsername='root', + MasterUserPassword='hunter2', + Port=1234, + DBSecurityGroups=["my_sg"]) + + replica = conn.create_db_instance_read_replica(DBInstanceIdentifier="db-replica-1", + SourceDBInstanceIdentifier="db-master-1", + DBInstanceClass="db.m1.small") + + mydb = conn.describe_db_instances(DBInstanceIdentifier=replica['DBInstance']['DBInstanceIdentifier'])['DBInstances'][0] + mydb['DBInstanceStatus'].should.equal('available') + # read-replicas are not allowed to be stopped at this time. + conn.stop_db_instance.when.called_with(DBInstanceIdentifier=mydb['DBInstanceIdentifier']).should.throw(ClientError) + # read-replicas are not allowed to be started at this time. + conn.start_db_instance.when.called_with(DBInstanceIdentifier=mydb['DBInstanceIdentifier']).should.throw(ClientError) @mock_rds2 diff --git a/tests/test_redshift/test_redshift.py b/tests/test_redshift/test_redshift.py index 1df503de2..cebaa3ec7 100644 --- a/tests/test_redshift/test_redshift.py +++ b/tests/test_redshift/test_redshift.py @@ -106,7 +106,7 @@ def test_create_single_node_cluster(): @mock_redshift_deprecated -def test_default_cluster_attibutes(): +def test_default_cluster_attributes(): conn = boto.redshift.connect_to_region("us-east-1") cluster_identifier = 'my_cluster' @@ -216,6 +216,33 @@ def test_create_cluster_with_security_group(): set(group_names).should.equal(set(["security_group1", "security_group2"])) +@mock_redshift +def test_create_cluster_with_security_group_boto3(): + client = boto3.client('redshift', region_name='us-east-1') + client.create_cluster_security_group( + ClusterSecurityGroupName="security_group1", + Description="This is my security group", + ) + client.create_cluster_security_group( + ClusterSecurityGroupName="security_group2", + Description="This is my security group", + ) + + cluster_identifier = 'my_cluster' + client.create_cluster( + ClusterIdentifier=cluster_identifier, + NodeType="dw.hs1.xlarge", + MasterUsername="username", + MasterUserPassword="password", + ClusterSecurityGroups=["security_group1", "security_group2"] + ) + response = client.describe_clusters(ClusterIdentifier=cluster_identifier) + cluster = response['Clusters'][0] + group_names = [group['ClusterSecurityGroupName'] + for group in cluster['ClusterSecurityGroups']] + set(group_names).should.equal({"security_group1", "security_group2"}) + + @mock_redshift_deprecated @mock_ec2_deprecated def test_create_cluster_with_vpc_security_groups(): @@ -242,6 +269,31 @@ def test_create_cluster_with_vpc_security_groups(): list(group_ids).should.equal([security_group.id]) +@mock_redshift +@mock_ec2 +def test_create_cluster_with_vpc_security_groups_boto3(): + ec2 = boto3.resource('ec2', region_name='us-east-1') + vpc = ec2.create_vpc(CidrBlock='10.0.0.0/16') + client = boto3.client('redshift', region_name='us-east-1') + cluster_id = 'my_cluster' + security_group = ec2.create_security_group( + Description="vpc_security_group", + GroupName="a group", + VpcId=vpc.id) + client.create_cluster( + ClusterIdentifier=cluster_id, + NodeType="dw.hs1.xlarge", + MasterUsername="username", + MasterUserPassword="password", + VpcSecurityGroupIds=[security_group.id], + ) + response = client.describe_clusters(ClusterIdentifier=cluster_id) + cluster = response['Clusters'][0] + group_ids = [group['VpcSecurityGroupId'] + for group in cluster['VpcSecurityGroups']] + list(group_ids).should.equal([security_group.id]) + + @mock_redshift_deprecated def test_create_cluster_with_parameter_group(): conn = boto.connect_redshift() @@ -267,7 +319,7 @@ def test_create_cluster_with_parameter_group(): @mock_redshift_deprecated -def test_describe_non_existant_cluster(): +def test_describe_non_existent_cluster(): conn = boto.redshift.connect_to_region("us-east-1") conn.describe_clusters.when.called_with( "not-a-cluster").should.throw(ClusterNotFound) @@ -391,7 +443,7 @@ def test_create_invalid_cluster_subnet_group(): @mock_redshift_deprecated -def test_describe_non_existant_subnet_group(): +def test_describe_non_existent_subnet_group(): conn = boto.redshift.connect_to_region("us-east-1") conn.describe_cluster_subnet_groups.when.called_with( "not-a-subnet-group").should.throw(ClusterSubnetGroupNotFound) @@ -447,7 +499,7 @@ def test_create_cluster_security_group(): @mock_redshift_deprecated -def test_describe_non_existant_security_group(): +def test_describe_non_existent_security_group(): conn = boto.redshift.connect_to_region("us-east-1") conn.describe_cluster_security_groups.when.called_with( "not-a-security-group").should.throw(ClusterSecurityGroupNotFound) @@ -498,7 +550,7 @@ def test_create_cluster_parameter_group(): @mock_redshift_deprecated -def test_describe_non_existant_parameter_group(): +def test_describe_non_existent_parameter_group(): conn = boto.redshift.connect_to_region("us-east-1") conn.describe_cluster_parameter_groups.when.called_with( "not-a-parameter-group").should.throw(ClusterParameterGroupNotFound) @@ -530,6 +582,17 @@ def test_delete_cluster_parameter_group(): "not-a-parameter-group").should.throw(ClusterParameterGroupNotFound) + +@mock_redshift +def test_create_cluster_snapshot_of_non_existent_cluster(): + client = boto3.client('redshift', region_name='us-east-1') + cluster_identifier = 'non-existent-cluster-id' + client.create_cluster_snapshot.when.called_with( + SnapshotIdentifier='snapshot-id', + ClusterIdentifier=cluster_identifier, + ).should.throw(ClientError, 'Cluster {} not found.'.format(cluster_identifier)) + + @mock_redshift def test_create_cluster_snapshot(): client = boto3.client('redshift', region_name='us-east-1') @@ -560,6 +623,52 @@ def test_create_cluster_snapshot(): snapshot['MasterUsername'].should.equal('username') +@mock_redshift +def test_describe_cluster_snapshots(): + client = boto3.client('redshift', region_name='us-east-1') + cluster_identifier = 'my_cluster' + snapshot_identifier = 'my_snapshot' + + client.create_cluster( + DBName='test-db', + ClusterIdentifier=cluster_identifier, + ClusterType='single-node', + NodeType='ds2.xlarge', + MasterUsername='username', + MasterUserPassword='password', + ) + + client.create_cluster_snapshot( + SnapshotIdentifier=snapshot_identifier, + ClusterIdentifier=cluster_identifier, + ) + + resp_clust = client.describe_cluster_snapshots(ClusterIdentifier=cluster_identifier) + resp_snap = client.describe_cluster_snapshots(SnapshotIdentifier=snapshot_identifier) + resp_clust['Snapshots'][0].should.equal(resp_snap['Snapshots'][0]) + snapshot = resp_snap['Snapshots'][0] + snapshot['SnapshotIdentifier'].should.equal(snapshot_identifier) + snapshot['ClusterIdentifier'].should.equal(cluster_identifier) + snapshot['NumberOfNodes'].should.equal(1) + snapshot['NodeType'].should.equal('ds2.xlarge') + snapshot['MasterUsername'].should.equal('username') + + +@mock_redshift +def test_describe_cluster_snapshots_not_found_error(): + client = boto3.client('redshift', region_name='us-east-1') + cluster_identifier = 'my_cluster' + snapshot_identifier = 'my_snapshot' + + client.describe_cluster_snapshots.when.called_with( + ClusterIdentifier=cluster_identifier, + ).should.throw(ClientError, 'Cluster {} not found.'.format(cluster_identifier)) + + client.describe_cluster_snapshots.when.called_with( + SnapshotIdentifier=snapshot_identifier + ).should.throw(ClientError, 'Snapshot {} not found.'.format(snapshot_identifier)) + + @mock_redshift def test_delete_cluster_snapshot(): client = boto3.client('redshift', region_name='us-east-1') @@ -652,6 +761,15 @@ def test_create_cluster_from_snapshot(): new_cluster['Endpoint']['Port'].should.equal(1234) +@mock_redshift +def test_create_cluster_from_non_existent_snapshot(): + client = boto3.client('redshift', region_name='us-east-1') + client.restore_from_cluster_snapshot.when.called_with( + ClusterIdentifier='cluster-id', + SnapshotIdentifier='non-existent-snapshot', + ).should.throw(ClientError, 'Snapshot non-existent-snapshot not found.') + + @mock_redshift def test_create_cluster_status_update(): client = boto3.client('redshift', region_name='us-east-1') @@ -673,12 +791,126 @@ def test_create_cluster_status_update(): @mock_redshift -def test_describe_snapshot_tags(): +def test_describe_tags_with_resource_type(): client = boto3.client('redshift', region_name='us-east-1') cluster_identifier = 'my_cluster' + cluster_arn = 'arn:aws:redshift:us-east-1:123456789012:' \ + 'cluster:{}'.format(cluster_identifier) snapshot_identifier = 'my_snapshot' + snapshot_arn = 'arn:aws:redshift:us-east-1:123456789012:' \ + 'snapshot:{}/{}'.format(cluster_identifier, + snapshot_identifier) tag_key = 'test-tag-key' - tag_value = 'teat-tag-value' + tag_value = 'test-tag-value' + + client.create_cluster( + DBName='test-db', + ClusterIdentifier=cluster_identifier, + ClusterType='single-node', + NodeType='ds2.xlarge', + MasterUsername='username', + MasterUserPassword='password', + Tags=[{'Key': tag_key, + 'Value': tag_value}] + ) + tags_response = client.describe_tags(ResourceType='cluster') + tagged_resources = tags_response['TaggedResources'] + list(tagged_resources).should.have.length_of(1) + tagged_resources[0]['ResourceType'].should.equal('cluster') + tagged_resources[0]['ResourceName'].should.equal(cluster_arn) + tag = tagged_resources[0]['Tag'] + tag['Key'].should.equal(tag_key) + tag['Value'].should.equal(tag_value) + + client.create_cluster_snapshot( + SnapshotIdentifier=snapshot_identifier, + ClusterIdentifier=cluster_identifier, + Tags=[{'Key': tag_key, + 'Value': tag_value}] + ) + tags_response = client.describe_tags(ResourceType='snapshot') + tagged_resources = tags_response['TaggedResources'] + list(tagged_resources).should.have.length_of(1) + tagged_resources[0]['ResourceType'].should.equal('snapshot') + tagged_resources[0]['ResourceName'].should.equal(snapshot_arn) + tag = tagged_resources[0]['Tag'] + tag['Key'].should.equal(tag_key) + tag['Value'].should.equal(tag_value) + + +@mock_redshift +def test_describe_tags_cannot_specify_resource_type_and_resource_name(): + client = boto3.client('redshift', region_name='us-east-1') + resource_name = 'arn:aws:redshift:us-east-1:123456789012:cluster:cluster-id' + resource_type = 'cluster' + client.describe_tags.when.called_with( + ResourceName=resource_name, + ResourceType=resource_type + ).should.throw(ClientError, 'using either an ARN or a resource type') + + +@mock_redshift +def test_describe_tags_with_resource_name(): + client = boto3.client('redshift', region_name='us-east-1') + cluster_identifier = 'cluster-id' + cluster_arn = 'arn:aws:redshift:us-east-1:123456789012:' \ + 'cluster:{}'.format(cluster_identifier) + snapshot_identifier = 'snapshot-id' + snapshot_arn = 'arn:aws:redshift:us-east-1:123456789012:' \ + 'snapshot:{}/{}'.format(cluster_identifier, + snapshot_identifier) + tag_key = 'test-tag-key' + tag_value = 'test-tag-value' + + client.create_cluster( + DBName='test-db', + ClusterIdentifier=cluster_identifier, + ClusterType='single-node', + NodeType='ds2.xlarge', + MasterUsername='username', + MasterUserPassword='password', + Tags=[{'Key': tag_key, + 'Value': tag_value}] + ) + tags_response = client.describe_tags(ResourceName=cluster_arn) + tagged_resources = tags_response['TaggedResources'] + list(tagged_resources).should.have.length_of(1) + tagged_resources[0]['ResourceType'].should.equal('cluster') + tagged_resources[0]['ResourceName'].should.equal(cluster_arn) + tag = tagged_resources[0]['Tag'] + tag['Key'].should.equal(tag_key) + tag['Value'].should.equal(tag_value) + + client.create_cluster_snapshot( + SnapshotIdentifier=snapshot_identifier, + ClusterIdentifier=cluster_identifier, + Tags=[{'Key': tag_key, + 'Value': tag_value}] + ) + tags_response = client.describe_tags(ResourceName=snapshot_arn) + tagged_resources = tags_response['TaggedResources'] + list(tagged_resources).should.have.length_of(1) + tagged_resources[0]['ResourceType'].should.equal('snapshot') + tagged_resources[0]['ResourceName'].should.equal(snapshot_arn) + tag = tagged_resources[0]['Tag'] + tag['Key'].should.equal(tag_key) + tag['Value'].should.equal(tag_value) + + +@mock_redshift +def test_create_tags(): + client = boto3.client('redshift', region_name='us-east-1') + cluster_identifier = 'cluster-id' + cluster_arn = 'arn:aws:redshift:us-east-1:123456789012:' \ + 'cluster:{}'.format(cluster_identifier) + tag_key = 'test-tag-key' + tag_value = 'test-tag-value' + num_tags = 5 + tags = [] + for i in range(0, num_tags): + tag = {'Key': '{}-{}'.format(tag_key, i), + 'Value': '{}-{}'.format(tag_value, i)} + tags.append(tag) client.create_cluster( DBName='test-db', @@ -688,17 +920,125 @@ def test_describe_snapshot_tags(): MasterUsername='username', MasterUserPassword='password', ) - - client.create_cluster_snapshot( - SnapshotIdentifier=snapshot_identifier, - ClusterIdentifier=cluster_identifier, - Tags=[{'Key': tag_key, - 'Value': tag_value}] + client.create_tags( + ResourceName=cluster_arn, + Tags=tags ) + response = client.describe_clusters(ClusterIdentifier=cluster_identifier) + cluster = response['Clusters'][0] + list(cluster['Tags']).should.have.length_of(num_tags) + response = client.describe_tags(ResourceName=cluster_arn) + list(response['TaggedResources']).should.have.length_of(num_tags) + + +@mock_redshift +def test_delete_tags(): + client = boto3.client('redshift', region_name='us-east-1') + cluster_identifier = 'cluster-id' + cluster_arn = 'arn:aws:redshift:us-east-1:123456789012:' \ + 'cluster:{}'.format(cluster_identifier) + tag_key = 'test-tag-key' + tag_value = 'test-tag-value' + tags = [] + for i in range(1, 2): + tag = {'Key': '{}-{}'.format(tag_key, i), + 'Value': '{}-{}'.format(tag_value, i)} + tags.append(tag) + + client.create_cluster( + DBName='test-db', + ClusterIdentifier=cluster_identifier, + ClusterType='single-node', + NodeType='ds2.xlarge', + MasterUsername='username', + MasterUserPassword='password', + Tags=tags + ) + client.delete_tags( + ResourceName=cluster_arn, + TagKeys=[tag['Key'] for tag in tags + if tag['Key'] != '{}-1'.format(tag_key)] + ) + response = client.describe_clusters(ClusterIdentifier=cluster_identifier) + cluster = response['Clusters'][0] + list(cluster['Tags']).should.have.length_of(1) + response = client.describe_tags(ResourceName=cluster_arn) + list(response['TaggedResources']).should.have.length_of(1) + + +@mock_ec2 +@mock_redshift +def test_describe_tags_all_resource_types(): + ec2 = boto3.resource('ec2', region_name='us-east-1') + vpc = ec2.create_vpc(CidrBlock='10.0.0.0/16') + subnet = ec2.create_subnet(VpcId=vpc.id, CidrBlock='10.0.0.0/24') + client = boto3.client('redshift', region_name='us-east-1') + response = client.describe_tags() + list(response['TaggedResources']).should.have.length_of(0) + client.create_cluster_subnet_group( + ClusterSubnetGroupName='my_subnet_group', + Description='This is my subnet group', + SubnetIds=[subnet.id], + Tags=[{'Key': 'tag_key', + 'Value': 'tag_value'}] + ) + client.create_cluster_security_group( + ClusterSecurityGroupName="security_group1", + Description="This is my security group", + Tags=[{'Key': 'tag_key', + 'Value': 'tag_value'}] + ) + client.create_cluster( + DBName='test', + ClusterIdentifier='my_cluster', + ClusterType='single-node', + NodeType='ds2.xlarge', + MasterUsername='user', + MasterUserPassword='password', + Tags=[{'Key': 'tag_key', + 'Value': 'tag_value'}] + ) + client.create_cluster_snapshot( + SnapshotIdentifier='my_snapshot', + ClusterIdentifier='my_cluster', + Tags=[{'Key': 'tag_key', + 'Value': 'tag_value'}] + ) + client.create_cluster_parameter_group( + ParameterGroupName="my_parameter_group", + ParameterGroupFamily="redshift-1.0", + Description="This is my parameter group", + Tags=[{'Key': 'tag_key', + 'Value': 'tag_value'}] + ) + response = client.describe_tags() + expected_types = ['cluster', 'parametergroup', 'securitygroup', 'snapshot', 'subnetgroup'] + tagged_resources = response['TaggedResources'] + returned_types = [resource['ResourceType'] for resource in tagged_resources] + list(tagged_resources).should.have.length_of(len(expected_types)) + set(returned_types).should.equal(set(expected_types)) + + +@mock_redshift +def test_tagged_resource_not_found_error(): + client = boto3.client('redshift', region_name='us-east-1') + + cluster_arn = 'arn:aws:redshift:us-east-1::cluster:fake' + client.describe_tags.when.called_with( + ResourceName=cluster_arn + ).should.throw(ClientError, 'cluster (fake) not found.') + + snapshot_arn = 'arn:aws:redshift:us-east-1::snapshot:cluster-id/snap-id' + client.delete_tags.when.called_with( + ResourceName=snapshot_arn, + TagKeys=['test'] + ).should.throw(ClientError, 'snapshot (snap-id) not found.') + + client.describe_tags.when.called_with( + ResourceType='cluster' + ).should.throw(ClientError, "resource of type 'cluster' not found.") + + client.describe_tags.when.called_with( + ResourceName='bad:arn' + ).should.throw(ClientError, "Tagging is not supported for this type of resource") - tags_response = client.describe_tags(ResourceType='Snapshot') - tagged_resources = tags_response['TaggedResources'] - list(tagged_resources).should.have.length_of(1) - tag = tagged_resources[0]['Tag'] - tag['Key'].should.equal(tag_key) - tag['Value'].should.equal(tag_value) diff --git a/tests/test_s3/test_s3.py b/tests/test_s3/test_s3.py index 619a60302..e4cb499b9 100644 --- a/tests/test_s3/test_s3.py +++ b/tests/test_s3/test_s3.py @@ -16,6 +16,7 @@ import boto3 from botocore.client import ClientError import botocore.exceptions from boto.exception import S3CreateError, S3ResponseError +from botocore.handlers import disable_signing from boto.s3.connection import S3Connection from boto.s3.key import Key from freezegun import freeze_time @@ -770,6 +771,14 @@ def test_list_versions(): versions[1].version_id.should.equal('1') versions[1].get_contents_as_string().should.equal(b"Version 2") + key = Key(bucket, 'the2-key') + key.set_contents_from_string("Version 1") + + keys = list(bucket.list()) + keys.should.have.length_of(2) + versions = list(bucket.list_versions(prefix='the2-')) + versions.should.have.length_of(1) + @mock_s3_deprecated def test_acl_setting(): @@ -856,6 +865,49 @@ def test_bucket_acl_switching(): g.permission == 'READ' for g in grants), grants +@mock_s3 +def test_s3_object_in_public_bucket(): + s3 = boto3.resource('s3') + bucket = s3.Bucket('test-bucket') + bucket.create(ACL='public-read') + bucket.put_object(Body=b'ABCD', Key='file.txt') + + s3_anonymous = boto3.resource('s3') + s3_anonymous.meta.client.meta.events.register('choose-signer.s3.*', disable_signing) + + contents = s3_anonymous.Object(key='file.txt', bucket_name='test-bucket').get()['Body'].read() + contents.should.equal(b'ABCD') + + bucket.put_object(ACL='private', Body=b'ABCD', Key='file.txt') + + with assert_raises(ClientError) as exc: + s3_anonymous.Object(key='file.txt', bucket_name='test-bucket').get() + exc.exception.response['Error']['Code'].should.equal('403') + + params = {'Bucket': 'test-bucket','Key': 'file.txt'} + presigned_url = boto3.client('s3').generate_presigned_url('get_object', params, ExpiresIn=900) + response = requests.get(presigned_url) + assert response.status_code == 200 + +@mock_s3 +def test_s3_object_in_private_bucket(): + s3 = boto3.resource('s3') + bucket = s3.Bucket('test-bucket') + bucket.create(ACL='private') + bucket.put_object(ACL='private', Body=b'ABCD', Key='file.txt') + + s3_anonymous = boto3.resource('s3') + s3_anonymous.meta.client.meta.events.register('choose-signer.s3.*', disable_signing) + + with assert_raises(ClientError) as exc: + s3_anonymous.Object(key='file.txt', bucket_name='test-bucket').get() + exc.exception.response['Error']['Code'].should.equal('403') + + bucket.put_object(ACL='public-read', Body=b'ABCD', Key='file.txt') + contents = s3_anonymous.Object(key='file.txt', bucket_name='test-bucket').get()['Body'].read() + contents.should.equal(b'ABCD') + + @mock_s3_deprecated def test_unicode_key(): conn = boto.connect_s3() @@ -1383,6 +1435,281 @@ def test_boto3_put_object_with_tagging(): resp['TagSet'].should.contain({'Key': 'foo', 'Value': 'bar'}) +@mock_s3 +def test_boto3_put_bucket_tagging(): + s3 = boto3.client("s3", region_name="us-east-1") + bucket_name = "mybucket" + s3.create_bucket(Bucket=bucket_name) + + # With 1 tag: + resp = s3.put_bucket_tagging(Bucket=bucket_name, + Tagging={ + "TagSet": [ + { + "Key": "TagOne", + "Value": "ValueOne" + } + ] + }) + resp['ResponseMetadata']['HTTPStatusCode'].should.equal(200) + + # With multiple tags: + resp = s3.put_bucket_tagging(Bucket=bucket_name, + Tagging={ + "TagSet": [ + { + "Key": "TagOne", + "Value": "ValueOne" + }, + { + "Key": "TagTwo", + "Value": "ValueTwo" + } + ] + }) + + resp['ResponseMetadata']['HTTPStatusCode'].should.equal(200) + + # No tags is also OK: + resp = s3.put_bucket_tagging(Bucket=bucket_name, Tagging={ + "TagSet": [] + }) + resp['ResponseMetadata']['HTTPStatusCode'].should.equal(200) + + +@mock_s3 +def test_boto3_get_bucket_tagging(): + s3 = boto3.client("s3", region_name="us-east-1") + bucket_name = "mybucket" + s3.create_bucket(Bucket=bucket_name) + s3.put_bucket_tagging(Bucket=bucket_name, + Tagging={ + "TagSet": [ + { + "Key": "TagOne", + "Value": "ValueOne" + }, + { + "Key": "TagTwo", + "Value": "ValueTwo" + } + ] + }) + + # Get the tags for the bucket: + resp = s3.get_bucket_tagging(Bucket=bucket_name) + resp['ResponseMetadata']['HTTPStatusCode'].should.equal(200) + len(resp["TagSet"]).should.equal(2) + + # With no tags: + s3.put_bucket_tagging(Bucket=bucket_name, Tagging={ + "TagSet": [] + }) + + with assert_raises(ClientError) as err: + s3.get_bucket_tagging(Bucket=bucket_name) + + e = err.exception + e.response["Error"]["Code"].should.equal("NoSuchTagSet") + e.response["Error"]["Message"].should.equal("The TagSet does not exist") + + +@mock_s3 +def test_boto3_delete_bucket_tagging(): + s3 = boto3.client("s3", region_name="us-east-1") + bucket_name = "mybucket" + s3.create_bucket(Bucket=bucket_name) + + s3.put_bucket_tagging(Bucket=bucket_name, + Tagging={ + "TagSet": [ + { + "Key": "TagOne", + "Value": "ValueOne" + }, + { + "Key": "TagTwo", + "Value": "ValueTwo" + } + ] + }) + + resp = s3.delete_bucket_tagging(Bucket=bucket_name) + resp['ResponseMetadata']['HTTPStatusCode'].should.equal(204) + + with assert_raises(ClientError) as err: + s3.get_bucket_tagging(Bucket=bucket_name) + + e = err.exception + e.response["Error"]["Code"].should.equal("NoSuchTagSet") + e.response["Error"]["Message"].should.equal("The TagSet does not exist") + + +@mock_s3 +def test_boto3_put_bucket_cors(): + s3 = boto3.client("s3", region_name="us-east-1") + bucket_name = "mybucket" + s3.create_bucket(Bucket=bucket_name) + + resp = s3.put_bucket_cors(Bucket=bucket_name, CORSConfiguration={ + "CORSRules": [ + { + "AllowedOrigins": [ + "*" + ], + "AllowedMethods": [ + "GET", + "POST" + ], + "AllowedHeaders": [ + "Authorization" + ], + "ExposeHeaders": [ + "x-amz-request-id" + ], + "MaxAgeSeconds": 123 + }, + { + "AllowedOrigins": [ + "*" + ], + "AllowedMethods": [ + "PUT" + ], + "AllowedHeaders": [ + "Authorization" + ], + "ExposeHeaders": [ + "x-amz-request-id" + ], + "MaxAgeSeconds": 123 + } + ] + }) + + resp['ResponseMetadata']['HTTPStatusCode'].should.equal(200) + + with assert_raises(ClientError) as err: + s3.put_bucket_cors(Bucket=bucket_name, CORSConfiguration={ + "CORSRules": [ + { + "AllowedOrigins": [ + "*" + ], + "AllowedMethods": [ + "NOTREAL", + "POST" + ] + } + ] + }) + e = err.exception + e.response["Error"]["Code"].should.equal("InvalidRequest") + e.response["Error"]["Message"].should.equal("Found unsupported HTTP method in CORS config. " + "Unsupported method is NOTREAL") + + with assert_raises(ClientError) as err: + s3.put_bucket_cors(Bucket=bucket_name, CORSConfiguration={ + "CORSRules": [] + }) + e = err.exception + e.response["Error"]["Code"].should.equal("MalformedXML") + + # And 101: + many_rules = [{"AllowedOrigins": ["*"], "AllowedMethods": ["GET"]}] * 101 + with assert_raises(ClientError) as err: + s3.put_bucket_cors(Bucket=bucket_name, CORSConfiguration={ + "CORSRules": many_rules + }) + e = err.exception + e.response["Error"]["Code"].should.equal("MalformedXML") + + +@mock_s3 +def test_boto3_get_bucket_cors(): + s3 = boto3.client("s3", region_name="us-east-1") + bucket_name = "mybucket" + s3.create_bucket(Bucket=bucket_name) + + # Without CORS: + with assert_raises(ClientError) as err: + s3.get_bucket_cors(Bucket=bucket_name) + + e = err.exception + e.response["Error"]["Code"].should.equal("NoSuchCORSConfiguration") + e.response["Error"]["Message"].should.equal("The CORS configuration does not exist") + + s3.put_bucket_cors(Bucket=bucket_name, CORSConfiguration={ + "CORSRules": [ + { + "AllowedOrigins": [ + "*" + ], + "AllowedMethods": [ + "GET", + "POST" + ], + "AllowedHeaders": [ + "Authorization" + ], + "ExposeHeaders": [ + "x-amz-request-id" + ], + "MaxAgeSeconds": 123 + }, + { + "AllowedOrigins": [ + "*" + ], + "AllowedMethods": [ + "PUT" + ], + "AllowedHeaders": [ + "Authorization" + ], + "ExposeHeaders": [ + "x-amz-request-id" + ], + "MaxAgeSeconds": 123 + } + ] + }) + + resp = s3.get_bucket_cors(Bucket=bucket_name) + resp['ResponseMetadata']['HTTPStatusCode'].should.equal(200) + len(resp["CORSRules"]).should.equal(2) + + +@mock_s3 +def test_boto3_delete_bucket_cors(): + s3 = boto3.client("s3", region_name="us-east-1") + bucket_name = "mybucket" + s3.create_bucket(Bucket=bucket_name) + s3.put_bucket_cors(Bucket=bucket_name, CORSConfiguration={ + "CORSRules": [ + { + "AllowedOrigins": [ + "*" + ], + "AllowedMethods": [ + "GET" + ] + } + ] + }) + + resp = s3.delete_bucket_cors(Bucket=bucket_name) + resp['ResponseMetadata']['HTTPStatusCode'].should.equal(204) + + # Verify deletion: + with assert_raises(ClientError) as err: + s3.get_bucket_cors(Bucket=bucket_name) + + e = err.exception + e.response["Error"]["Code"].should.equal("NoSuchCORSConfiguration") + e.response["Error"]["Message"].should.equal("The CORS configuration does not exist") + + @mock_s3 def test_boto3_put_object_tagging(): s3 = boto3.client('s3', region_name='us-east-1') diff --git a/tests/test_s3/test_server.py b/tests/test_s3/test_server.py index c3ca3c3ff..9c8252a04 100644 --- a/tests/test_s3/test_server.py +++ b/tests/test_s3/test_server.py @@ -3,6 +3,7 @@ from __future__ import unicode_literals import sure # noqa +from flask.testing import FlaskClient import moto.server as server ''' @@ -10,18 +11,28 @@ Test the different server responses ''' -def test_s3_server_get(): - backend = server.create_backend_app("s3") - test_client = backend.test_client() +class AuthenticatedClient(FlaskClient): + def open(self, *args, **kwargs): + kwargs['headers'] = kwargs.get('headers', {}) + kwargs['headers']['Authorization'] = "Any authorization header" + return super(AuthenticatedClient, self).open(*args, **kwargs) + +def authenticated_client(): + backend = server.create_backend_app("s3") + backend.test_client_class = AuthenticatedClient + return backend.test_client() + + +def test_s3_server_get(): + test_client = authenticated_client() res = test_client.get('/') res.data.should.contain(b'ListAllMyBucketsResult') def test_s3_server_bucket_create(): - backend = server.create_backend_app("s3") - test_client = backend.test_client() + test_client = authenticated_client() res = test_client.put('/', 'http://foobaz.localhost:5000/') res.status_code.should.equal(200) @@ -44,8 +55,7 @@ def test_s3_server_bucket_create(): def test_s3_server_bucket_versioning(): - backend = server.create_backend_app("s3") - test_client = backend.test_client() + test_client = authenticated_client() # Just enough XML to enable versioning body = 'Enabled' @@ -55,8 +65,7 @@ def test_s3_server_bucket_versioning(): def test_s3_server_post_to_bucket(): - backend = server.create_backend_app("s3") - test_client = backend.test_client() + test_client = authenticated_client() res = test_client.put('/', 'http://tester.localhost:5000/') res.status_code.should.equal(200) @@ -72,8 +81,7 @@ def test_s3_server_post_to_bucket(): def test_s3_server_post_without_content_length(): - backend = server.create_backend_app("s3") - test_client = backend.test_client() + test_client = authenticated_client() res = test_client.put('/', 'http://tester.localhost:5000/', environ_overrides={'CONTENT_LENGTH': ''}) res.status_code.should.equal(411) diff --git a/tests/test_s3bucket_path/test_bucket_path_server.py b/tests/test_s3bucket_path/test_bucket_path_server.py index c67a2bcaa..434110e87 100644 --- a/tests/test_s3bucket_path/test_bucket_path_server.py +++ b/tests/test_s3bucket_path/test_bucket_path_server.py @@ -1,6 +1,7 @@ from __future__ import unicode_literals import sure # noqa +from flask.testing import FlaskClient import moto.server as server ''' @@ -8,9 +9,21 @@ Test the different server responses ''' -def test_s3_server_get(): +class AuthenticatedClient(FlaskClient): + def open(self, *args, **kwargs): + kwargs['headers'] = kwargs.get('headers', {}) + kwargs['headers']['Authorization'] = "Any authorization header" + return super(AuthenticatedClient, self).open(*args, **kwargs) + + +def authenticated_client(): backend = server.create_backend_app("s3bucket_path") - test_client = backend.test_client() + backend.test_client_class = AuthenticatedClient + return backend.test_client() + + +def test_s3_server_get(): + test_client = authenticated_client() res = test_client.get('/') @@ -18,8 +31,7 @@ def test_s3_server_get(): def test_s3_server_bucket_create(): - backend = server.create_backend_app("s3bucket_path") - test_client = backend.test_client() + test_client = authenticated_client() res = test_client.put('/foobar', 'http://localhost:5000') res.status_code.should.equal(200) @@ -54,8 +66,7 @@ def test_s3_server_bucket_create(): def test_s3_server_post_to_bucket(): - backend = server.create_backend_app("s3bucket_path") - test_client = backend.test_client() + test_client = authenticated_client() res = test_client.put('/foobar2', 'http://localhost:5000/') res.status_code.should.equal(200) @@ -71,8 +82,7 @@ def test_s3_server_post_to_bucket(): def test_s3_server_put_ipv6(): - backend = server.create_backend_app("s3bucket_path") - test_client = backend.test_client() + test_client = authenticated_client() res = test_client.put('/foobar2', 'http://[::]:5000/') res.status_code.should.equal(200) @@ -88,8 +98,7 @@ def test_s3_server_put_ipv6(): def test_s3_server_put_ipv4(): - backend = server.create_backend_app("s3bucket_path") - test_client = backend.test_client() + test_client = authenticated_client() res = test_client.put('/foobar2', 'http://127.0.0.1:5000/') res.status_code.should.equal(200) diff --git a/tests/test_sns/test_application_boto3.py b/tests/test_sns/test_application_boto3.py index 99c378fe4..1c9695fea 100644 --- a/tests/test_sns/test_application_boto3.py +++ b/tests/test_sns/test_application_boto3.py @@ -321,3 +321,30 @@ def test_publish_to_disabled_platform_endpoint(): MessageStructure="json", TargetArn=endpoint_arn, ).should.throw(ClientError) + + +@mock_sns +def test_set_sms_attributes(): + conn = boto3.client('sns', region_name='us-east-1') + + conn.set_sms_attributes(attributes={'DefaultSMSType': 'Transactional', 'test': 'test'}) + + response = conn.get_sms_attributes() + response.should.contain('attributes') + response['attributes'].should.contain('DefaultSMSType') + response['attributes'].should.contain('test') + response['attributes']['DefaultSMSType'].should.equal('Transactional') + response['attributes']['test'].should.equal('test') + + +@mock_sns +def test_get_sms_attributes_filtered(): + conn = boto3.client('sns', region_name='us-east-1') + + conn.set_sms_attributes(attributes={'DefaultSMSType': 'Transactional', 'test': 'test'}) + + response = conn.get_sms_attributes(attributes=['DefaultSMSType']) + response.should.contain('attributes') + response['attributes'].should.contain('DefaultSMSType') + response['attributes'].should_not.contain('test') + response['attributes']['DefaultSMSType'].should.equal('Transactional') diff --git a/tests/test_sns/test_publishing.py b/tests/test_sns/test_publishing.py index dd75ff4be..b626e2fac 100644 --- a/tests/test_sns/test_publishing.py +++ b/tests/test_sns/test_publishing.py @@ -2,6 +2,7 @@ from __future__ import unicode_literals from six.moves.urllib.parse import parse_qs import boto +import re from freezegun import freeze_time import sure # noqa @@ -9,6 +10,9 @@ from moto.packages.responses import responses from moto import mock_sns_deprecated, mock_sqs_deprecated +MESSAGE_FROM_SQS_TEMPLATE = '{\n "Message": "%s",\n "MessageId": "%s",\n "Signature": "EXAMPLElDMXvB8r9R83tGoNn0ecwd5UjllzsvSvbItzfaMpN2nk5HVSw7XnOn/49IkxDKz8YrlH2qJXj2iZB0Zo2O71c4qQk1fMUDi3LGpij7RCW7AW9vYYsSqIKRnFS94ilu7NFhUzLiieYr4BKHpdTmdD6c0esKEYBpabxDSc=",\n "SignatureVersion": "1",\n "SigningCertURL": "https://sns.us-east-1.amazonaws.com/SimpleNotificationService-f3ecfb7224c7233fe7bb5f59f96de52f.pem",\n "Subject": "my subject",\n "Timestamp": "2015-01-01T12:00:00.000Z",\n "TopicArn": "arn:aws:sns:%s:123456789012:some-topic",\n "Type": "Notification",\n "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"\n}' + + @mock_sqs_deprecated @mock_sns_deprecated def test_publish_to_sqs(): @@ -24,11 +28,16 @@ def test_publish_to_sqs(): conn.subscribe(topic_arn, "sqs", "arn:aws:sqs:us-east-1:123456789012:test-queue") - conn.publish(topic=topic_arn, message="my message") + message_to_publish = 'my message' + with freeze_time("2015-01-01 12:00:00"): + published_message = conn.publish(topic=topic_arn, message=message_to_publish) + published_message_id = published_message['PublishResponse']['PublishResult']['MessageId'] queue = sqs_conn.get_queue("test-queue") message = queue.read(1) - message.get_body().should.equal('my message') + expected = MESSAGE_FROM_SQS_TEMPLATE % (message_to_publish, published_message_id, 'us-east-1') + acquired_message = re.sub("\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z", '2015-01-01T12:00:00.000Z', message.get_body()) + acquired_message.should.equal(expected) @mock_sqs_deprecated @@ -46,8 +55,14 @@ def test_publish_to_sqs_in_different_region(): conn.subscribe(topic_arn, "sqs", "arn:aws:sqs:us-west-2:123456789012:test-queue") - conn.publish(topic=topic_arn, message="my message") + message_to_publish = 'my message' + with freeze_time("2015-01-01 12:00:00"): + published_message = conn.publish(topic=topic_arn, message=message_to_publish) + published_message_id = published_message['PublishResponse']['PublishResult']['MessageId'] queue = sqs_conn.get_queue("test-queue") message = queue.read(1) - message.get_body().should.equal('my message') + expected = MESSAGE_FROM_SQS_TEMPLATE % (message_to_publish, published_message_id, 'us-west-1') + + acquired_message = re.sub("\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z", '2015-01-01T12:00:00.000Z', message.get_body()) + acquired_message.should.equal(expected) diff --git a/tests/test_sns/test_publishing_boto3.py b/tests/test_sns/test_publishing_boto3.py index 00c9ac7e2..6228f212f 100644 --- a/tests/test_sns/test_publishing_boto3.py +++ b/tests/test_sns/test_publishing_boto3.py @@ -5,11 +5,17 @@ import json from six.moves.urllib.parse import parse_qs import boto3 +import re from freezegun import freeze_time import sure # noqa from moto.packages.responses import responses +from botocore.exceptions import ClientError from moto import mock_sns, mock_sqs +from freezegun import freeze_time + + +MESSAGE_FROM_SQS_TEMPLATE = '{\n "Message": "%s",\n "MessageId": "%s",\n "Signature": "EXAMPLElDMXvB8r9R83tGoNn0ecwd5UjllzsvSvbItzfaMpN2nk5HVSw7XnOn/49IkxDKz8YrlH2qJXj2iZB0Zo2O71c4qQk1fMUDi3LGpij7RCW7AW9vYYsSqIKRnFS94ilu7NFhUzLiieYr4BKHpdTmdD6c0esKEYBpabxDSc=",\n "SignatureVersion": "1",\n "SigningCertURL": "https://sns.us-east-1.amazonaws.com/SimpleNotificationService-f3ecfb7224c7233fe7bb5f59f96de52f.pem",\n "Subject": "my subject",\n "Timestamp": "2015-01-01T12:00:00.000Z",\n "TopicArn": "arn:aws:sns:%s:123456789012:some-topic",\n "Type": "Notification",\n "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"\n}' @mock_sqs @@ -26,12 +32,96 @@ def test_publish_to_sqs(): conn.subscribe(TopicArn=topic_arn, Protocol="sqs", Endpoint="arn:aws:sqs:us-east-1:123456789012:test-queue") - - conn.publish(TopicArn=topic_arn, Message="my message") + message = 'my message' + with freeze_time("2015-01-01 12:00:00"): + published_message = conn.publish(TopicArn=topic_arn, Message=message) + published_message_id = published_message['MessageId'] queue = sqs_conn.get_queue_by_name(QueueName="test-queue") messages = queue.receive_messages(MaxNumberOfMessages=1) - messages[0].body.should.equal('my message') + expected = MESSAGE_FROM_SQS_TEMPLATE % (message, published_message_id, 'us-east-1') + acquired_message = re.sub("\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z", u'2015-01-01T12:00:00.000Z', messages[0].body) + acquired_message.should.equal(expected) + + +@mock_sns +def test_publish_sms(): + client = boto3.client('sns', region_name='us-east-1') + client.create_topic(Name="some-topic") + resp = client.create_topic(Name="some-topic") + arn = resp['TopicArn'] + + client.subscribe( + TopicArn=arn, + Protocol='sms', + Endpoint='+15551234567' + ) + + result = client.publish(PhoneNumber="+15551234567", Message="my message") + result.should.contain('MessageId') + + +@mock_sns +def test_publish_bad_sms(): + client = boto3.client('sns', region_name='us-east-1') + client.create_topic(Name="some-topic") + resp = client.create_topic(Name="some-topic") + arn = resp['TopicArn'] + + client.subscribe( + TopicArn=arn, + Protocol='sms', + Endpoint='+15551234567' + ) + + try: + # Test invalid number + client.publish(PhoneNumber="NAA+15551234567", Message="my message") + except ClientError as err: + err.response['Error']['Code'].should.equal('InvalidParameter') + + try: + # Test not found number + client.publish(PhoneNumber="+44001234567", Message="my message") + except ClientError as err: + err.response['Error']['Code'].should.equal('ParameterValueInvalid') + + +@mock_sqs +@mock_sns +def test_publish_to_sqs_dump_json(): + conn = boto3.client('sns', region_name='us-east-1') + conn.create_topic(Name="some-topic") + response = conn.list_topics() + topic_arn = response["Topics"][0]['TopicArn'] + + sqs_conn = boto3.resource('sqs', region_name='us-east-1') + sqs_conn.create_queue(QueueName="test-queue") + + conn.subscribe(TopicArn=topic_arn, + Protocol="sqs", + Endpoint="arn:aws:sqs:us-east-1:123456789012:test-queue") + + message = json.dumps({ + "Records": [{ + "eventVersion": "2.0", + "eventSource": "aws:s3", + "s3": { + "s3SchemaVersion": "1.0" + } + }] + }, sort_keys=True) + with freeze_time("2015-01-01 12:00:00"): + published_message = conn.publish(TopicArn=topic_arn, Message=message) + published_message_id = published_message['MessageId'] + + queue = sqs_conn.get_queue_by_name(QueueName="test-queue") + messages = queue.receive_messages(MaxNumberOfMessages=1) + + escaped = message.replace('"', '\\"') + expected = MESSAGE_FROM_SQS_TEMPLATE % (escaped, published_message_id, 'us-east-1') + acquired_message = re.sub("\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z", u'2015-01-01T12:00:00.000Z', messages[0].body) + acquired_message.should.equal(expected) @mock_sqs @@ -49,11 +139,16 @@ def test_publish_to_sqs_in_different_region(): Protocol="sqs", Endpoint="arn:aws:sqs:us-west-2:123456789012:test-queue") - conn.publish(TopicArn=topic_arn, Message="my message") + message = 'my message' + with freeze_time("2015-01-01 12:00:00"): + published_message = conn.publish(TopicArn=topic_arn, Message=message) + published_message_id = published_message['MessageId'] queue = sqs_conn.get_queue_by_name(QueueName="test-queue") messages = queue.receive_messages(MaxNumberOfMessages=1) - messages[0].body.should.equal('my message') + expected = MESSAGE_FROM_SQS_TEMPLATE % (message, published_message_id, 'us-west-1') + acquired_message = re.sub("\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z", u'2015-01-01T12:00:00.000Z', messages[0].body) + acquired_message.should.equal(expected) @freeze_time("2013-01-01") diff --git a/tests/test_sns/test_subscriptions.py b/tests/test_sns/test_subscriptions.py index 292fd83c0..ba241ba44 100644 --- a/tests/test_sns/test_subscriptions.py +++ b/tests/test_sns/test_subscriptions.py @@ -34,6 +34,7 @@ def test_creating_subscription(): "ListSubscriptionsResult"]["Subscriptions"] subscriptions.should.have.length_of(0) + @mock_sns_deprecated def test_deleting_subscriptions_by_deleting_topic(): conn = boto.connect_sns() @@ -66,6 +67,7 @@ def test_deleting_subscriptions_by_deleting_topic(): "ListSubscriptionsResult"]["Subscriptions"] subscriptions.should.have.length_of(0) + @mock_sns_deprecated def test_getting_subscriptions_by_topic(): conn = boto.connect_sns() diff --git a/tests/test_sns/test_subscriptions_boto3.py b/tests/test_sns/test_subscriptions_boto3.py index ac325ed20..4446febfc 100644 --- a/tests/test_sns/test_subscriptions_boto3.py +++ b/tests/test_sns/test_subscriptions_boto3.py @@ -1,12 +1,49 @@ from __future__ import unicode_literals import boto3 +import json import sure # noqa +from botocore.exceptions import ClientError +from nose.tools import assert_raises + from moto import mock_sns from moto.sns.models import DEFAULT_PAGE_SIZE +@mock_sns +def test_subscribe_sms(): + client = boto3.client('sns', region_name='us-east-1') + client.create_topic(Name="some-topic") + resp = client.create_topic(Name="some-topic") + arn = resp['TopicArn'] + + resp = client.subscribe( + TopicArn=arn, + Protocol='sms', + Endpoint='+15551234567' + ) + resp.should.contain('SubscriptionArn') + + +@mock_sns +def test_subscribe_bad_sms(): + client = boto3.client('sns', region_name='us-east-1') + client.create_topic(Name="some-topic") + resp = client.create_topic(Name="some-topic") + arn = resp['TopicArn'] + + try: + # Test invalid number + client.subscribe( + TopicArn=arn, + Protocol='sms', + Endpoint='NAA+15551234567' + ) + except ClientError as err: + err.response['Error']['Code'].should.equal('InvalidParameter') + + @mock_sns def test_creating_subscription(): conn = boto3.client('sns', region_name='us-east-1') @@ -33,6 +70,7 @@ def test_creating_subscription(): subscriptions = conn.list_subscriptions()["Subscriptions"] subscriptions.should.have.length_of(0) + @mock_sns def test_deleting_subscriptions_by_deleting_topic(): conn = boto3.client('sns', region_name='us-east-1') @@ -64,6 +102,7 @@ def test_deleting_subscriptions_by_deleting_topic(): subscriptions = conn.list_subscriptions()["Subscriptions"] subscriptions.should.have.length_of(0) + @mock_sns def test_getting_subscriptions_by_topic(): conn = boto3.client('sns', region_name='us-east-1') @@ -124,3 +163,136 @@ def test_subscription_paging(): topic1_subscriptions["Subscriptions"].should.have.length_of( int(DEFAULT_PAGE_SIZE / 3)) topic1_subscriptions.shouldnt.have("NextToken") + + +@mock_sns +def test_set_subscription_attributes(): + conn = boto3.client('sns', region_name='us-east-1') + conn.create_topic(Name="some-topic") + response = conn.list_topics() + topic_arn = response["Topics"][0]['TopicArn'] + + conn.subscribe(TopicArn=topic_arn, + Protocol="http", + Endpoint="http://example.com/") + + subscriptions = conn.list_subscriptions()["Subscriptions"] + subscriptions.should.have.length_of(1) + subscription = subscriptions[0] + subscription["TopicArn"].should.equal(topic_arn) + subscription["Protocol"].should.equal("http") + subscription["SubscriptionArn"].should.contain(topic_arn) + subscription["Endpoint"].should.equal("http://example.com/") + + subscription_arn = subscription["SubscriptionArn"] + attrs = conn.get_subscription_attributes( + SubscriptionArn=subscription_arn + ) + attrs.should.have.key('Attributes') + conn.set_subscription_attributes( + SubscriptionArn=subscription_arn, + AttributeName='RawMessageDelivery', + AttributeValue='true' + ) + delivery_policy = json.dumps({ + 'healthyRetryPolicy': { + "numRetries": 10, + "minDelayTarget": 1, + "maxDelayTarget":2 + } + }) + conn.set_subscription_attributes( + SubscriptionArn=subscription_arn, + AttributeName='DeliveryPolicy', + AttributeValue=delivery_policy + ) + attrs = conn.get_subscription_attributes( + SubscriptionArn=subscription_arn + ) + attrs['Attributes']['RawMessageDelivery'].should.equal('true') + attrs['Attributes']['DeliveryPolicy'].should.equal(delivery_policy) + + # not existing subscription + with assert_raises(ClientError): + conn.set_subscription_attributes( + SubscriptionArn='invalid', + AttributeName='RawMessageDelivery', + AttributeValue='true' + ) + with assert_raises(ClientError): + attrs = conn.get_subscription_attributes( + SubscriptionArn='invalid' + ) + + + # invalid attr name + with assert_raises(ClientError): + conn.set_subscription_attributes( + SubscriptionArn=subscription_arn, + AttributeName='InvalidName', + AttributeValue='true' + ) + + +@mock_sns +def test_check_not_opted_out(): + conn = boto3.client('sns', region_name='us-east-1') + response = conn.check_if_phone_number_is_opted_out(phoneNumber='+447428545375') + + response.should.contain('isOptedOut') + response['isOptedOut'].should.be(False) + + +@mock_sns +def test_check_opted_out(): + # Phone number ends in 99 so is hardcoded in the endpoint to return opted + # out status + conn = boto3.client('sns', region_name='us-east-1') + response = conn.check_if_phone_number_is_opted_out(phoneNumber='+447428545399') + + response.should.contain('isOptedOut') + response['isOptedOut'].should.be(True) + + +@mock_sns +def test_check_opted_out_invalid(): + conn = boto3.client('sns', region_name='us-east-1') + + # Invalid phone number + with assert_raises(ClientError): + conn.check_if_phone_number_is_opted_out(phoneNumber='+44742LALALA') + + +@mock_sns +def test_list_opted_out(): + conn = boto3.client('sns', region_name='us-east-1') + response = conn.list_phone_numbers_opted_out() + + response.should.contain('phoneNumbers') + len(response['phoneNumbers']).should.be.greater_than(0) + + +@mock_sns +def test_opt_in(): + conn = boto3.client('sns', region_name='us-east-1') + response = conn.list_phone_numbers_opted_out() + current_len = len(response['phoneNumbers']) + assert current_len > 0 + + conn.opt_in_phone_number(phoneNumber=response['phoneNumbers'][0]) + + response = conn.list_phone_numbers_opted_out() + len(response['phoneNumbers']).should.be.greater_than(0) + len(response['phoneNumbers']).should.be.lower_than(current_len) + + +@mock_sns +def test_confirm_subscription(): + conn = boto3.client('sns', region_name='us-east-1') + response = conn.create_topic(Name='testconfirm') + + conn.confirm_subscription( + TopicArn=response['TopicArn'], + Token='2336412f37fb687f5d51e6e241d59b68c4e583a5cee0be6f95bbf97ab8d2441cf47b99e848408adaadf4c197e65f03473d53c4ba398f6abbf38ce2e8ebf7b4ceceb2cd817959bcde1357e58a2861b05288c535822eb88cac3db04f592285249971efc6484194fc4a4586147f16916692', + AuthenticateOnUnsubscribe='true' + ) diff --git a/tests/test_sns/test_topics_boto3.py b/tests/test_sns/test_topics_boto3.py index 4702744c3..a9c2a2904 100644 --- a/tests/test_sns/test_topics_boto3.py +++ b/tests/test_sns/test_topics_boto3.py @@ -129,3 +129,20 @@ def test_topic_paging(): response.shouldnt.have("NextToken") topics_list.should.have.length_of(int(DEFAULT_PAGE_SIZE / 2)) + + +@mock_sns +def test_add_remove_permissions(): + conn = boto3.client('sns', region_name='us-east-1') + response = conn.create_topic(Name='testpermissions') + + conn.add_permission( + TopicArn=response['TopicArn'], + Label='Test1234', + AWSAccountId=['999999999999'], + ActionName=['AddPermission'] + ) + conn.remove_permission( + TopicArn=response['TopicArn'], + Label='Test1234' + ) diff --git a/tests/test_sqs/test_sqs.py b/tests/test_sqs/test_sqs.py index 3eb8e2213..9c439eb68 100644 --- a/tests/test_sqs/test_sqs.py +++ b/tests/test_sqs/test_sqs.py @@ -8,7 +8,6 @@ from boto.exception import SQSError from boto.sqs.message import RawMessage, Message import base64 -import requests import sure # noqa import time @@ -18,6 +17,39 @@ import tests.backport_assert_raises # noqa from nose.tools import assert_raises +@mock_sqs +def test_create_fifo_queue_fail(): + sqs = boto3.client('sqs', region_name='us-east-1') + + try: + sqs.create_queue( + QueueName='test-queue', + Attributes={ + 'FifoQueue': 'true', + } + ) + except botocore.exceptions.ClientError as err: + err.response['Error']['Code'].should.equal('InvalidParameterValue') + else: + raise RuntimeError('Should of raised InvalidParameterValue Exception') + +@mock_sqs +def test_create_fifo_queue(): + sqs = boto3.client('sqs', region_name='us-east-1') + resp = sqs.create_queue( + QueueName='test-queue.fifo', + Attributes={ + 'FifoQueue': 'true', + } + ) + queue_url = resp['QueueUrl'] + + response = sqs.get_queue_attributes(QueueUrl=queue_url) + response['Attributes'].should.contain('FifoQueue') + response['Attributes']['FifoQueue'].should.equal('true') + + + @mock_sqs def test_create_queue(): sqs = boto3.resource('sqs', region_name='us-east-1') @@ -39,6 +71,7 @@ def test_get_inexistent_queue(): sqs.get_queue_by_name.when.called_with( QueueName='nonexisting-queue').should.throw(botocore.exceptions.ClientError) + @mock_sqs def test_message_send_without_attributes(): sqs = boto3.resource('sqs', region_name='us-east-1') @@ -56,6 +89,7 @@ def test_message_send_without_attributes(): messages = queue.receive_messages() messages.should.have.length_of(1) + @mock_sqs def test_message_send_with_attributes(): sqs = boto3.resource('sqs', region_name='us-east-1') @@ -229,6 +263,7 @@ def test_send_receive_message_without_attributes(): message1.shouldnt.have.key('MD5OfMessageAttributes') message2.shouldnt.have.key('MD5OfMessageAttributes') + @mock_sqs def test_send_receive_message_with_attributes(): sqs = boto3.resource('sqs', region_name='us-east-1') diff --git a/tests/test_ssm/test_ssm_boto3.py b/tests/test_ssm/test_ssm_boto3.py index 418c58708..7f4aca533 100644 --- a/tests/test_ssm/test_ssm_boto3.py +++ b/tests/test_ssm/test_ssm_boto3.py @@ -1,6 +1,7 @@ from __future__ import unicode_literals import boto3 +import botocore.exceptions import sure # noqa from moto import mock_ssm @@ -25,6 +26,27 @@ def test_delete_parameter(): len(response['Parameters']).should.equal(0) +@mock_ssm +def test_delete_parameters(): + client = boto3.client('ssm', region_name='us-east-1') + + client.put_parameter( + Name='test', + Description='A test parameter', + Value='value', + Type='String') + + response = client.get_parameters(Names=['test']) + len(response['Parameters']).should.equal(1) + + result = client.delete_parameters(Names=['test', 'invalid']) + len(result['DeletedParameters']).should.equal(1) + len(result['InvalidParameters']).should.equal(1) + + response = client.get_parameters(Names=['test']) + len(response['Parameters']).should.equal(0) + + @mock_ssm def test_put_parameter(): client = boto3.client('ssm', region_name='us-east-1') @@ -47,6 +69,39 @@ def test_put_parameter(): response['Parameters'][0]['Type'].should.equal('String') +@mock_ssm +def test_get_parameter(): + client = boto3.client('ssm', region_name='us-east-1') + + client.put_parameter( + Name='test', + Description='A test parameter', + Value='value', + Type='String') + + response = client.get_parameter( + Name='test', + WithDecryption=False) + + response['Parameter']['Name'].should.equal('test') + response['Parameter']['Value'].should.equal('value') + response['Parameter']['Type'].should.equal('String') + + +@mock_ssm +def test_get_nonexistant_parameter(): + client = boto3.client('ssm', region_name='us-east-1') + + try: + client.get_parameter( + Name='test_noexist', + WithDecryption=False) + raise RuntimeError('Should of failed') + except botocore.exceptions.ClientError as err: + err.operation_name.should.equal('GetParameter') + err.response['Error']['Message'].should.equal('Parameter test_noexist not found.') + + @mock_ssm def test_describe_parameters(): client = boto3.client('ssm', region_name='us-east-1') @@ -142,7 +197,6 @@ def test_describe_parameters_filter_type(): p['KeyId'] = 'a key' client.put_parameter(**p) - response = client.describe_parameters(Filters=[ { 'Key': 'Type', @@ -169,7 +223,6 @@ def test_describe_parameters_filter_keyid(): p['KeyId'] = "key:%d" % i client.put_parameter(**p) - response = client.describe_parameters(Filters=[ { 'Key': 'KeyId', @@ -182,6 +235,20 @@ def test_describe_parameters_filter_keyid(): ''.should.equal(response.get('NextToken', '')) +@mock_ssm +def test_get_parameter_invalid(): + client = client = boto3.client('ssm', region_name='us-east-1') + response = client.get_parameters( + Names=[ + 'invalid' + ], + WithDecryption=False) + + len(response['Parameters']).should.equal(0) + len(response['InvalidParameters']).should.equal(1) + response['InvalidParameters'][0].should.equal('invalid') + + @mock_ssm def test_put_parameter_secure_default_kms(): client = boto3.client('ssm', region_name='us-east-1') @@ -248,6 +315,7 @@ def test_put_parameter_secure_custom_kms(): response['Parameters'][0]['Value'].should.equal('value') response['Parameters'][0]['Type'].should.equal('SecureString') + @mock_ssm def test_add_remove_list_tags_for_resource(): client = boto3.client('ssm', region_name='us-east-1') diff --git a/tests/test_xray/test_xray_boto3.py b/tests/test_xray/test_xray_boto3.py new file mode 100644 index 000000000..5ad8f8bc7 --- /dev/null +++ b/tests/test_xray/test_xray_boto3.py @@ -0,0 +1,139 @@ +from __future__ import unicode_literals + +import boto3 +import json +import botocore.exceptions +import sure # noqa + +from moto import mock_xray + +import datetime + + +@mock_xray +def test_put_telemetry(): + client = boto3.client('xray', region_name='us-east-1') + + client.put_telemetry_records( + TelemetryRecords=[ + { + 'Timestamp': datetime.datetime(2015, 1, 1), + 'SegmentsReceivedCount': 123, + 'SegmentsSentCount': 123, + 'SegmentsSpilloverCount': 123, + 'SegmentsRejectedCount': 123, + 'BackendConnectionErrors': { + 'TimeoutCount': 123, + 'ConnectionRefusedCount': 123, + 'HTTPCode4XXCount': 123, + 'HTTPCode5XXCount': 123, + 'UnknownHostCount': 123, + 'OtherCount': 123 + } + }, + ], + EC2InstanceId='string', + Hostname='string', + ResourceARN='string' + ) + + +@mock_xray +def test_put_trace_segments(): + client = boto3.client('xray', region_name='us-east-1') + + client.put_trace_segments( + TraceSegmentDocuments=[ + json.dumps({ + 'name': 'example.com', + 'id': '70de5b6f19ff9a0a', + 'start_time': 1.478293361271E9, + 'trace_id': '1-581cf771-a006649127e371903a2de979', + 'end_time': 1.478293361449E9 + }) + ] + ) + + +@mock_xray +def test_trace_summary(): + client = boto3.client('xray', region_name='us-east-1') + + client.put_trace_segments( + TraceSegmentDocuments=[ + json.dumps({ + 'name': 'example.com', + 'id': '70de5b6f19ff9a0a', + 'start_time': 1.478293361271E9, + 'trace_id': '1-581cf771-a006649127e371903a2de979', + 'in_progress': True + }), + json.dumps({ + 'name': 'example.com', + 'id': '70de5b6f19ff9a0b', + 'start_time': 1478293365, + 'trace_id': '1-581cf771-a006649127e371903a2de979', + 'end_time': 1478293385 + }) + ] + ) + + client.get_trace_summaries( + StartTime=datetime.datetime(2014, 1, 1), + EndTime=datetime.datetime(2017, 1, 1) + ) + + +@mock_xray +def test_batch_get_trace(): + client = boto3.client('xray', region_name='us-east-1') + + client.put_trace_segments( + TraceSegmentDocuments=[ + json.dumps({ + 'name': 'example.com', + 'id': '70de5b6f19ff9a0a', + 'start_time': 1.478293361271E9, + 'trace_id': '1-581cf771-a006649127e371903a2de979', + 'in_progress': True + }), + json.dumps({ + 'name': 'example.com', + 'id': '70de5b6f19ff9a0b', + 'start_time': 1478293365, + 'trace_id': '1-581cf771-a006649127e371903a2de979', + 'end_time': 1478293385 + }) + ] + ) + + resp = client.batch_get_traces( + TraceIds=['1-581cf771-a006649127e371903a2de979', '1-581cf772-b006649127e371903a2de979'] + ) + len(resp['UnprocessedTraceIds']).should.equal(1) + len(resp['Traces']).should.equal(1) + + +# Following are not implemented, just testing it returns what boto expects +@mock_xray +def test_batch_get_service_graph(): + client = boto3.client('xray', region_name='us-east-1') + + client.get_service_graph( + StartTime=datetime.datetime(2014, 1, 1), + EndTime=datetime.datetime(2017, 1, 1) + ) + + +@mock_xray +def test_batch_get_trace_graph(): + client = boto3.client('xray', region_name='us-east-1') + + client.batch_get_traces( + TraceIds=['1-581cf771-a006649127e371903a2de979', '1-581cf772-b006649127e371903a2de979'] + ) + + + + + diff --git a/travis_moto_server.sh b/travis_moto_server.sh new file mode 100755 index 000000000..902644b20 --- /dev/null +++ b/travis_moto_server.sh @@ -0,0 +1,5 @@ +#!/usr/bin/env bash +set -e +pip install flask +pip install /moto/dist/moto*.gz +moto_server -H 0.0.0.0 -p 5000 \ No newline at end of file diff --git a/wait_for.py b/wait_for.py new file mode 100755 index 000000000..ea3639d16 --- /dev/null +++ b/wait_for.py @@ -0,0 +1,31 @@ +import time + +try: + # py2 + import urllib2 as urllib + from urllib2 import URLError + import socket + import httplib + + EXCEPTIONS = (URLError, socket.error, httplib.BadStatusLine) +except ImportError: + # py3 + import urllib.request as urllib + from urllib.error import URLError + + EXCEPTIONS = (URLError, ConnectionResetError) + + +start_ts = time.time() +print("Waiting for service to come up") +while True: + try: + urllib.urlopen('http://localhost:5000/', timeout=1) + break + except EXCEPTIONS: + elapsed_s = time.time() - start_ts + if elapsed_s > 30: + raise + + print('.') + time.sleep(1)