Merge branch 'master' into mark-alias-target
This commit is contained in:
commit
414ff930ed
70
.travis.yml
70
.travis.yml
@ -2,36 +2,56 @@ dist: xenial
|
||||
language: python
|
||||
sudo: false
|
||||
services:
|
||||
- docker
|
||||
- docker
|
||||
python:
|
||||
- 2.7
|
||||
- 3.6
|
||||
- 3.7
|
||||
- 2.7
|
||||
- 3.6
|
||||
- 3.7
|
||||
env:
|
||||
- TEST_SERVER_MODE=false
|
||||
- TEST_SERVER_MODE=true
|
||||
- TEST_SERVER_MODE=false
|
||||
- TEST_SERVER_MODE=true
|
||||
before_install:
|
||||
- export BOTO_CONFIG=/dev/null
|
||||
- export BOTO_CONFIG=/dev/null
|
||||
install:
|
||||
# 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
|
||||
- |
|
||||
python setup.py sdist
|
||||
|
||||
if [ "$TEST_SERVER_MODE" = "true" ]; then
|
||||
docker run --rm -t --name motoserver -e TEST_SERVER_MODE=true -e AWS_SECRET_ACCESS_KEY=server_secret -e AWS_ACCESS_KEY_ID=server_key -v `pwd`:/moto -p 5000:5000 -v /var/run/docker.sock:/var/run/docker.sock python:${TRAVIS_PYTHON_VERSION}-stretch /moto/travis_moto_server.sh &
|
||||
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
|
||||
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 &
|
||||
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
|
||||
if [ "$TEST_SERVER_MODE" = "true" ]; then
|
||||
python wait_for.py
|
||||
fi
|
||||
script:
|
||||
- make test
|
||||
- make test
|
||||
after_success:
|
||||
- coveralls
|
||||
- coveralls
|
||||
before_deploy:
|
||||
- git checkout $TRAVIS_BRANCH
|
||||
- git fetch --unshallow
|
||||
- python update_version_from_git.py
|
||||
deploy:
|
||||
- provider: pypi
|
||||
distributions: sdist bdist_wheel
|
||||
user: spulec
|
||||
password:
|
||||
secure: NxnPylnTfekJmGyoufCw0lMoYRskSMJzvAIyAlJJVYKwEhmiCPOrdy5qV8i8mRZ1AkUsqU3jBZ/PD56n96clHW0E3d080UleRDj6JpyALVdeLfMqZl9kLmZ8bqakWzYq3VSJKw2zGP/L4tPGf8wTK1SUv9yl/YNDsBdCkjDverw=
|
||||
on:
|
||||
branch:
|
||||
- master
|
||||
skip_cleanup: true
|
||||
skip_existing: true
|
||||
- provider: pypi
|
||||
distributions: sdist bdist_wheel
|
||||
user: spulec
|
||||
password:
|
||||
secure: NxnPylnTfekJmGyoufCw0lMoYRskSMJzvAIyAlJJVYKwEhmiCPOrdy5qV8i8mRZ1AkUsqU3jBZ/PD56n96clHW0E3d080UleRDj6JpyALVdeLfMqZl9kLmZ8bqakWzYq3VSJKw2zGP/L4tPGf8wTK1SUv9yl/YNDsBdCkjDverw=
|
||||
on:
|
||||
tags: true
|
||||
skip_existing: true
|
||||
|
@ -2012,23 +2012,23 @@
|
||||
- [ ] upload_archive
|
||||
- [ ] upload_multipart_part
|
||||
|
||||
## glue - 0% implemented
|
||||
- [ ] batch_create_partition
|
||||
## glue - 23% implemented
|
||||
- [x] batch_create_partition
|
||||
- [ ] batch_delete_connection
|
||||
- [ ] batch_delete_partition
|
||||
- [ ] batch_delete_table
|
||||
- [x] batch_delete_partition
|
||||
- [x] batch_delete_table
|
||||
- [ ] batch_delete_table_version
|
||||
- [ ] batch_get_partition
|
||||
- [ ] batch_stop_job_run
|
||||
- [ ] create_classifier
|
||||
- [ ] create_connection
|
||||
- [ ] create_crawler
|
||||
- [ ] create_database
|
||||
- [x] create_database
|
||||
- [ ] create_dev_endpoint
|
||||
- [ ] create_job
|
||||
- [ ] create_partition
|
||||
- [x] create_partition
|
||||
- [ ] create_script
|
||||
- [ ] create_table
|
||||
- [x] create_table
|
||||
- [ ] create_trigger
|
||||
- [ ] create_user_defined_function
|
||||
- [ ] delete_classifier
|
||||
@ -2037,8 +2037,8 @@
|
||||
- [ ] delete_database
|
||||
- [ ] delete_dev_endpoint
|
||||
- [ ] delete_job
|
||||
- [ ] delete_partition
|
||||
- [ ] delete_table
|
||||
- [x] delete_partition
|
||||
- [x] delete_table
|
||||
- [ ] delete_table_version
|
||||
- [ ] delete_trigger
|
||||
- [ ] delete_user_defined_function
|
||||
@ -2050,7 +2050,7 @@
|
||||
- [ ] get_crawler
|
||||
- [ ] get_crawler_metrics
|
||||
- [ ] get_crawlers
|
||||
- [ ] get_database
|
||||
- [x] get_database
|
||||
- [ ] get_databases
|
||||
- [ ] get_dataflow_graph
|
||||
- [ ] get_dev_endpoint
|
||||
@ -2060,13 +2060,13 @@
|
||||
- [ ] get_job_runs
|
||||
- [ ] get_jobs
|
||||
- [ ] get_mapping
|
||||
- [ ] get_partition
|
||||
- [ ] get_partitions
|
||||
- [x] get_partition
|
||||
- [x] get_partitions
|
||||
- [ ] get_plan
|
||||
- [ ] get_table
|
||||
- [ ] get_table_version
|
||||
- [ ] get_table_versions
|
||||
- [ ] get_tables
|
||||
- [x] get_table
|
||||
- [x] get_table_version
|
||||
- [x] get_table_versions
|
||||
- [x] get_tables
|
||||
- [ ] get_trigger
|
||||
- [ ] get_triggers
|
||||
- [ ] get_user_defined_function
|
||||
@ -2087,8 +2087,8 @@
|
||||
- [ ] update_database
|
||||
- [ ] update_dev_endpoint
|
||||
- [ ] update_job
|
||||
- [ ] update_partition
|
||||
- [ ] update_table
|
||||
- [x] update_partition
|
||||
- [x] update_table
|
||||
- [ ] update_trigger
|
||||
- [ ] update_user_defined_function
|
||||
|
||||
|
186
README.md
186
README.md
@ -55,95 +55,95 @@ With the decorator wrapping the test, all the calls to s3 are automatically mock
|
||||
It gets even better! Moto isn't just for Python code and it isn't just for S3. Look at the [standalone server mode](https://github.com/spulec/moto#stand-alone-server-mode) for more information about running Moto with other languages. Here's the status of the other AWS services implemented:
|
||||
|
||||
```gherkin
|
||||
|------------------------------------------------------------------------------|
|
||||
| Service Name | Decorator | Development Status |
|
||||
|------------------------------------------------------------------------------|
|
||||
| ACM | @mock_acm | all endpoints done |
|
||||
|------------------------------------------------------------------------------|
|
||||
| API Gateway | @mock_apigateway | core endpoints done |
|
||||
|------------------------------------------------------------------------------|
|
||||
| Autoscaling | @mock_autoscaling| core endpoints done |
|
||||
|------------------------------------------------------------------------------|
|
||||
| Cloudformation | @mock_cloudformation| core endpoints done |
|
||||
|------------------------------------------------------------------------------|
|
||||
| Cloudwatch | @mock_cloudwatch | basic endpoints done |
|
||||
|------------------------------------------------------------------------------|
|
||||
| CloudwatchEvents | @mock_events | all endpoints done |
|
||||
|------------------------------------------------------------------------------|
|
||||
| Cognito Identity | @mock_cognitoidentity| basic endpoints done |
|
||||
|------------------------------------------------------------------------------|
|
||||
| Cognito Identity Provider | @mock_cognitoidp| basic endpoints done |
|
||||
|------------------------------------------------------------------------------|
|
||||
| Config | @mock_config | basic endpoints done |
|
||||
|------------------------------------------------------------------------------|
|
||||
| Data Pipeline | @mock_datapipeline| basic endpoints done |
|
||||
|------------------------------------------------------------------------------|
|
||||
| DynamoDB | @mock_dynamodb | core endpoints done |
|
||||
| DynamoDB2 | @mock_dynamodb2 | all endpoints + partial indexes |
|
||||
|------------------------------------------------------------------------------|
|
||||
| EC2 | @mock_ec2 | core endpoints done |
|
||||
| - AMI | | core endpoints done |
|
||||
| - EBS | | core endpoints done |
|
||||
| - Instances | | all endpoints done |
|
||||
| - 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 | all endpoints done |
|
||||
|------------------------------------------------------------------------------|
|
||||
| EMR | @mock_emr | core endpoints done |
|
||||
|------------------------------------------------------------------------------|
|
||||
| Glacier | @mock_glacier | core endpoints done |
|
||||
|------------------------------------------------------------------------------|
|
||||
| IAM | @mock_iam | core endpoints done |
|
||||
|------------------------------------------------------------------------------|
|
||||
| IoT | @mock_iot | core endpoints done |
|
||||
| | @mock_iotdata | core endpoints done |
|
||||
|------------------------------------------------------------------------------|
|
||||
| Lambda | @mock_lambda | basic endpoints done, requires |
|
||||
| | | docker |
|
||||
|------------------------------------------------------------------------------|
|
||||
| Logs | @mock_logs | basic endpoints done |
|
||||
|------------------------------------------------------------------------------|
|
||||
| Kinesis | @mock_kinesis | core endpoints done |
|
||||
|------------------------------------------------------------------------------|
|
||||
| KMS | @mock_kms | basic endpoints done |
|
||||
|------------------------------------------------------------------------------|
|
||||
| Organizations | @mock_organizations | some core endpoints done |
|
||||
|------------------------------------------------------------------------------|
|
||||
| Polly | @mock_polly | all endpoints done |
|
||||
|------------------------------------------------------------------------------|
|
||||
| RDS | @mock_rds | core endpoints done |
|
||||
|------------------------------------------------------------------------------|
|
||||
| RDS2 | @mock_rds2 | core endpoints done |
|
||||
|------------------------------------------------------------------------------|
|
||||
| Redshift | @mock_redshift | core endpoints done |
|
||||
|------------------------------------------------------------------------------|
|
||||
| Route53 | @mock_route53 | core endpoints done |
|
||||
|------------------------------------------------------------------------------|
|
||||
| S3 | @mock_s3 | core endpoints done |
|
||||
|------------------------------------------------------------------------------|
|
||||
| SecretsManager | @mock_secretsmanager | basic endpoints done
|
||||
|------------------------------------------------------------------------------|
|
||||
| SES | @mock_ses | all endpoints done |
|
||||
|------------------------------------------------------------------------------|
|
||||
| SNS | @mock_sns | all endpoints done |
|
||||
|------------------------------------------------------------------------------|
|
||||
| SQS | @mock_sqs | core endpoints done |
|
||||
|------------------------------------------------------------------------------|
|
||||
| SSM | @mock_ssm | core endpoints done |
|
||||
|------------------------------------------------------------------------------|
|
||||
| STS | @mock_sts | core endpoints done |
|
||||
|------------------------------------------------------------------------------|
|
||||
| SWF | @mock_swf | basic endpoints done |
|
||||
|------------------------------------------------------------------------------|
|
||||
| X-Ray | @mock_xray | all endpoints done |
|
||||
|------------------------------------------------------------------------------|
|
||||
|-------------------------------------------------------------------------------------|
|
||||
| Service Name | Decorator | Development Status |
|
||||
|-------------------------------------------------------------------------------------|
|
||||
| ACM | @mock_acm | all endpoints done |
|
||||
|-------------------------------------------------------------------------------------|
|
||||
| API Gateway | @mock_apigateway | core endpoints done |
|
||||
|-------------------------------------------------------------------------------------|
|
||||
| Autoscaling | @mock_autoscaling | core endpoints done |
|
||||
|-------------------------------------------------------------------------------------|
|
||||
| Cloudformation | @mock_cloudformation | core endpoints done |
|
||||
|-------------------------------------------------------------------------------------|
|
||||
| Cloudwatch | @mock_cloudwatch | basic endpoints done |
|
||||
|-------------------------------------------------------------------------------------|
|
||||
| CloudwatchEvents | @mock_events | all endpoints done |
|
||||
|-------------------------------------------------------------------------------------|
|
||||
| Cognito Identity | @mock_cognitoidentity | basic endpoints done |
|
||||
|-------------------------------------------------------------------------------------|
|
||||
| Cognito Identity Provider | @mock_cognitoidp | basic endpoints done |
|
||||
|-------------------------------------------------------------------------------------|
|
||||
| Config | @mock_config | basic endpoints done |
|
||||
|-------------------------------------------------------------------------------------|
|
||||
| Data Pipeline | @mock_datapipeline | basic endpoints done |
|
||||
|-------------------------------------------------------------------------------------|
|
||||
| DynamoDB | @mock_dynamodb | core endpoints done |
|
||||
| DynamoDB2 | @mock_dynamodb2 | all endpoints + partial indexes |
|
||||
|-------------------------------------------------------------------------------------|
|
||||
| EC2 | @mock_ec2 | core endpoints done |
|
||||
| - AMI | | core endpoints done |
|
||||
| - EBS | | core endpoints done |
|
||||
| - Instances | | all endpoints done |
|
||||
| - 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 | all endpoints done |
|
||||
|-------------------------------------------------------------------------------------|
|
||||
| EMR | @mock_emr | core endpoints done |
|
||||
|-------------------------------------------------------------------------------------|
|
||||
| Glacier | @mock_glacier | core endpoints done |
|
||||
|-------------------------------------------------------------------------------------|
|
||||
| IAM | @mock_iam | core endpoints done |
|
||||
|-------------------------------------------------------------------------------------|
|
||||
| IoT | @mock_iot | core endpoints done |
|
||||
| | @mock_iotdata | core endpoints done |
|
||||
|-------------------------------------------------------------------------------------|
|
||||
| Kinesis | @mock_kinesis | core endpoints done |
|
||||
|-------------------------------------------------------------------------------------|
|
||||
| KMS | @mock_kms | basic endpoints done |
|
||||
|-------------------------------------------------------------------------------------|
|
||||
| Lambda | @mock_lambda | basic endpoints done, requires |
|
||||
| | | docker |
|
||||
|-------------------------------------------------------------------------------------|
|
||||
| Logs | @mock_logs | basic endpoints done |
|
||||
|-------------------------------------------------------------------------------------|
|
||||
| Organizations | @mock_organizations | some core endpoints done |
|
||||
|-------------------------------------------------------------------------------------|
|
||||
| Polly | @mock_polly | all endpoints done |
|
||||
|-------------------------------------------------------------------------------------|
|
||||
| RDS | @mock_rds | core endpoints done |
|
||||
|-------------------------------------------------------------------------------------|
|
||||
| RDS2 | @mock_rds2 | core endpoints done |
|
||||
|-------------------------------------------------------------------------------------|
|
||||
| Redshift | @mock_redshift | core endpoints done |
|
||||
|-------------------------------------------------------------------------------------|
|
||||
| Route53 | @mock_route53 | core endpoints done |
|
||||
|-------------------------------------------------------------------------------------|
|
||||
| S3 | @mock_s3 | core endpoints done |
|
||||
|-------------------------------------------------------------------------------------|
|
||||
| SecretsManager | @mock_secretsmanager | basic endpoints done |
|
||||
|-------------------------------------------------------------------------------------|
|
||||
| SES | @mock_ses | all endpoints done |
|
||||
|-------------------------------------------------------------------------------------|
|
||||
| SNS | @mock_sns | all endpoints done |
|
||||
|-------------------------------------------------------------------------------------|
|
||||
| SQS | @mock_sqs | core endpoints done |
|
||||
|-------------------------------------------------------------------------------------|
|
||||
| SSM | @mock_ssm | core endpoints done |
|
||||
|-------------------------------------------------------------------------------------|
|
||||
| STS | @mock_sts | core endpoints done |
|
||||
|-------------------------------------------------------------------------------------|
|
||||
| SWF | @mock_swf | basic endpoints done |
|
||||
|-------------------------------------------------------------------------------------|
|
||||
| X-Ray | @mock_xray | all endpoints done |
|
||||
|-------------------------------------------------------------------------------------|
|
||||
```
|
||||
|
||||
For a full list of endpoint [implementation coverage](https://github.com/spulec/moto/blob/master/IMPLEMENTATION_COVERAGE.md)
|
||||
@ -318,3 +318,11 @@ boto3.resource(
|
||||
```console
|
||||
$ pip install moto
|
||||
```
|
||||
|
||||
## Releases
|
||||
|
||||
Releases are done from travisci. Fairly closely following this:
|
||||
https://docs.travis-ci.com/user/deployment/pypi/
|
||||
|
||||
- Commits to `master` branch do a dev deploy to pypi.
|
||||
- Commits to a tag do a real deploy to pypi.
|
||||
|
149
docs/index.rst
149
docs/index.rst
@ -17,66 +17,95 @@ with ``moto`` and its usage.
|
||||
Currently implemented Services:
|
||||
-------------------------------
|
||||
|
||||
+-----------------------+---------------------+-----------------------------------+
|
||||
| Service Name | Decorator | Development Status |
|
||||
+=======================+=====================+===================================+
|
||||
| API Gateway | @mock_apigateway | core endpoints done |
|
||||
+-----------------------+---------------------+-----------------------------------+
|
||||
| Autoscaling | @mock_autoscaling | core endpoints done |
|
||||
+-----------------------+---------------------+-----------------------------------+
|
||||
| Cloudformation | @mock_cloudformation| core endpoints done |
|
||||
+-----------------------+---------------------+-----------------------------------+
|
||||
| Cloudwatch | @mock_cloudwatch | basic endpoints done |
|
||||
+-----------------------+---------------------+-----------------------------------+
|
||||
| Data Pipeline | @mock_datapipeline | basic endpoints done |
|
||||
+-----------------------+---------------------+-----------------------------------+
|
||||
| - DynamoDB | - @mock_dynamodb | - core endpoints done |
|
||||
| - DynamoDB2 | - @mock_dynamodb2 | - core endpoints + partial indexes|
|
||||
+-----------------------+---------------------+-----------------------------------+
|
||||
| EC2 | @mock_ec2 | core endpoints done |
|
||||
| - AMI | | - core endpoints done |
|
||||
| - EBS | | - core endpoints done |
|
||||
| - Instances | | - all endpoints done |
|
||||
| - Security Groups | | - core endpoints done |
|
||||
| - Tags | | - all endpoints done |
|
||||
+-----------------------+---------------------+-----------------------------------+
|
||||
| ECS | @mock_ecs | basic endpoints done |
|
||||
+-----------------------+---------------------+-----------------------------------+
|
||||
| ELB | @mock_elb | core endpoints done |
|
||||
| | @mock_elbv2 | core endpoints done |
|
||||
+-----------------------+---------------------+-----------------------------------+
|
||||
| EMR | @mock_emr | core endpoints done |
|
||||
+-----------------------+---------------------+-----------------------------------+
|
||||
| Glacier | @mock_glacier | core endpoints done |
|
||||
+-----------------------+---------------------+-----------------------------------+
|
||||
| IAM | @mock_iam | core endpoints done |
|
||||
+-----------------------+---------------------+-----------------------------------+
|
||||
| Lambda | @mock_lambda | basic endpoints done |
|
||||
+-----------------------+---------------------+-----------------------------------+
|
||||
| Kinesis | @mock_kinesis | core endpoints done |
|
||||
+-----------------------+---------------------+-----------------------------------+
|
||||
| KMS | @mock_kms | basic endpoints done |
|
||||
+-----------------------+---------------------+-----------------------------------+
|
||||
| RDS | @mock_rds | core endpoints done |
|
||||
+-----------------------+---------------------+-----------------------------------+
|
||||
| RDS2 | @mock_rds2 | core endpoints done |
|
||||
+-----------------------+---------------------+-----------------------------------+
|
||||
| Redshift | @mock_redshift | core endpoints done |
|
||||
+-----------------------+---------------------+-----------------------------------+
|
||||
| Route53 | @mock_route53 | core endpoints done |
|
||||
+-----------------------+---------------------+-----------------------------------+
|
||||
| S3 | @mock_s3 | core endpoints done |
|
||||
+-----------------------+---------------------+-----------------------------------+
|
||||
| SES | @mock_ses | core endpoints done |
|
||||
+-----------------------+---------------------+-----------------------------------+
|
||||
| SNS | @mock_sns | core endpoints done |
|
||||
+-----------------------+---------------------+-----------------------------------+
|
||||
| SQS | @mock_sqs | core endpoints done |
|
||||
+-----------------------+---------------------+-----------------------------------+
|
||||
| STS | @mock_sts | core endpoints done |
|
||||
+-----------------------+---------------------+-----------------------------------+
|
||||
| SWF | @mock_swf | basic endpoints done |
|
||||
+-----------------------+---------------------+-----------------------------------+
|
||||
+---------------------------+-----------------------+------------------------------------+
|
||||
| Service Name | Decorator | Development Status |
|
||||
+===========================+=======================+====================================+
|
||||
| ACM | @mock_acm | all endpoints done |
|
||||
+---------------------------+-----------------------+------------------------------------+
|
||||
| API Gateway | @mock_apigateway | core endpoints done |
|
||||
+---------------------------+-----------------------+------------------------------------+
|
||||
| Autoscaling | @mock_autoscaling | core endpoints done |
|
||||
+---------------------------+-----------------------+------------------------------------+
|
||||
| Cloudformation | @mock_cloudformation | core endpoints done |
|
||||
+---------------------------+-----------------------+------------------------------------+
|
||||
| Cloudwatch | @mock_cloudwatch | basic endpoints done |
|
||||
+---------------------------+-----------------------+------------------------------------+
|
||||
| CloudwatchEvents | @mock_events | all endpoints done |
|
||||
+---------------------------+-----------------------+------------------------------------+
|
||||
| Cognito Identity | @mock_cognitoidentity | all endpoints done |
|
||||
+---------------------------+-----------------------+------------------------------------+
|
||||
| Cognito Identity Provider | @mock_cognitoidp | all endpoints done |
|
||||
+---------------------------+-----------------------+------------------------------------+
|
||||
| Config | @mock_config | basic endpoints done |
|
||||
+---------------------------+-----------------------+------------------------------------+
|
||||
| Data Pipeline | @mock_datapipeline | basic endpoints done |
|
||||
+---------------------------+-----------------------+------------------------------------+
|
||||
| DynamoDB | - @mock_dynamodb | - core endpoints done |
|
||||
| DynamoDB2 | - @mock_dynamodb2 | - core endpoints + partial indexes |
|
||||
+---------------------------+-----------------------+------------------------------------+
|
||||
| EC2 | @mock_ec2 | core endpoints done |
|
||||
| - AMI | | - core endpoints done |
|
||||
| - EBS | | - core endpoints done |
|
||||
| - Instances | | - all endpoints done |
|
||||
| - 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 | all endpoints done |
|
||||
+---------------------------+-----------------------+------------------------------------+
|
||||
| EMR | @mock_emr | core endpoints done |
|
||||
+---------------------------+-----------------------+------------------------------------+
|
||||
| Glacier | @mock_glacier | core endpoints done |
|
||||
+---------------------------+-----------------------+------------------------------------+
|
||||
| IAM | @mock_iam | core endpoints done |
|
||||
+---------------------------+-----------------------+------------------------------------+
|
||||
| IoT | @mock_iot | core endpoints done |
|
||||
| | @mock_iotdata | core endpoints done |
|
||||
+---------------------------+-----------------------+------------------------------------+
|
||||
| Kinesis | @mock_kinesis | core endpoints done |
|
||||
+---------------------------+-----------------------+------------------------------------+
|
||||
| KMS | @mock_kms | basic endpoints done |
|
||||
+---------------------------+-----------------------+------------------------------------+
|
||||
| Lambda | @mock_lambda | basic endpoints done, |
|
||||
| | | requires docker |
|
||||
+---------------------------+-----------------------+------------------------------------+
|
||||
| Logs | @mock_logs | basic endpoints done |
|
||||
+---------------------------+-----------------------+------------------------------------+
|
||||
| Organizations | @mock_organizations | some core edpoints done |
|
||||
+---------------------------+-----------------------+------------------------------------+
|
||||
| Polly | @mock_polly | all endpoints done |
|
||||
+---------------------------+-----------------------+------------------------------------+
|
||||
| RDS | @mock_rds | core endpoints done |
|
||||
+---------------------------+-----------------------+------------------------------------+
|
||||
| RDS2 | @mock_rds2 | core endpoints done |
|
||||
+---------------------------+-----------------------+------------------------------------+
|
||||
| Redshift | @mock_redshift | core endpoints done |
|
||||
+---------------------------+-----------------------+------------------------------------+
|
||||
| Route53 | @mock_route53 | core endpoints done |
|
||||
+---------------------------+-----------------------+------------------------------------+
|
||||
| S3 | @mock_s3 | core endpoints done |
|
||||
+---------------------------+-----------------------+------------------------------------+
|
||||
| SecretsManager | @mock_secretsmanager | basic endpoints done |
|
||||
+---------------------------+-----------------------+------------------------------------+
|
||||
| SES | @mock_ses | all endpoints done |
|
||||
+---------------------------+-----------------------+------------------------------------+
|
||||
| SNS | @mock_sns | all endpoints done |
|
||||
+---------------------------+-----------------------+------------------------------------+
|
||||
| SQS | @mock_sqs | core endpoints done |
|
||||
+---------------------------+-----------------------+------------------------------------+
|
||||
| SSM | @mock_ssm | core endpoints done |
|
||||
+---------------------------+-----------------------+------------------------------------+
|
||||
| STS | @mock_sts | core endpoints done |
|
||||
+---------------------------+-----------------------+------------------------------------+
|
||||
| SWF | @mock_swf | basic endpoints done |
|
||||
+---------------------------+-----------------------+------------------------------------+
|
||||
| X-Ray | @mock_xray | all endpoints done |
|
||||
+---------------------------+-----------------------+------------------------------------+
|
||||
|
||||
|
||||
|
||||
|
@ -3,7 +3,7 @@ import logging
|
||||
# logging.getLogger('boto').setLevel(logging.CRITICAL)
|
||||
|
||||
__title__ = 'moto'
|
||||
__version__ = '1.3.8'
|
||||
__version__ = '1.3.9'
|
||||
|
||||
from .acm import mock_acm # flake8: noqa
|
||||
from .apigateway import mock_apigateway, mock_apigateway_deprecated # flake8: noqa
|
||||
|
@ -246,7 +246,8 @@ def resource_name_property_from_type(resource_type):
|
||||
|
||||
|
||||
def generate_resource_name(resource_type, stack_name, logical_id):
|
||||
if resource_type == "AWS::ElasticLoadBalancingV2::TargetGroup":
|
||||
if resource_type in ["AWS::ElasticLoadBalancingV2::TargetGroup",
|
||||
"AWS::ElasticLoadBalancingV2::LoadBalancer"]:
|
||||
# Target group names need to be less than 32 characters, so when cloudformation creates a name for you
|
||||
# it makes sure to stay under that limit
|
||||
name_prefix = '{0}-{1}'.format(stack_name, logical_id)
|
||||
|
@ -4,6 +4,7 @@ import six
|
||||
import random
|
||||
import yaml
|
||||
import os
|
||||
import string
|
||||
|
||||
from cfnlint import decode, core
|
||||
|
||||
@ -29,7 +30,7 @@ def generate_stackset_arn(stackset_id, region_name):
|
||||
|
||||
def random_suffix():
|
||||
size = 12
|
||||
chars = list(range(10)) + ['A-Z']
|
||||
chars = list(range(10)) + list(string.ascii_uppercase)
|
||||
return ''.join(six.text_type(random.choice(chars)) for x in range(size))
|
||||
|
||||
|
||||
|
@ -275,7 +275,7 @@ GET_METRIC_STATISTICS_TEMPLATE = """<GetMetricStatisticsResponse xmlns="http://m
|
||||
<Label>{{ label }}</Label>
|
||||
<Datapoints>
|
||||
{% for datapoint in datapoints %}
|
||||
<Datapoint>
|
||||
<member>
|
||||
{% if datapoint.sum is not none %}
|
||||
<Sum>{{ datapoint.sum }}</Sum>
|
||||
{% endif %}
|
||||
@ -302,7 +302,7 @@ GET_METRIC_STATISTICS_TEMPLATE = """<GetMetricStatisticsResponse xmlns="http://m
|
||||
|
||||
<Timestamp>{{ datapoint.timestamp }}</Timestamp>
|
||||
<Unit>{{ datapoint.unit }}</Unit>
|
||||
</Datapoint>
|
||||
</member>
|
||||
{% endfor %}
|
||||
</Datapoints>
|
||||
</GetMetricStatisticsResult>
|
||||
|
@ -724,7 +724,7 @@ class Table(BaseModel):
|
||||
if idx_col_set.issubset(set(hash_set.attrs)):
|
||||
yield hash_set
|
||||
|
||||
def scan(self, filters, limit, exclusive_start_key, filter_expression=None, index_name=None):
|
||||
def scan(self, filters, limit, exclusive_start_key, filter_expression=None, index_name=None, projection_expression=None):
|
||||
results = []
|
||||
scanned_count = 0
|
||||
all_indexes = self.all_indexes()
|
||||
@ -763,6 +763,14 @@ class Table(BaseModel):
|
||||
if passes_all_conditions:
|
||||
results.append(item)
|
||||
|
||||
if projection_expression:
|
||||
expressions = [x.strip() for x in projection_expression.split(',')]
|
||||
results = copy.deepcopy(results)
|
||||
for result in results:
|
||||
for attr in list(result.attrs):
|
||||
if attr not in expressions:
|
||||
result.attrs.pop(attr)
|
||||
|
||||
results, last_evaluated_key = self._trim_results(results, limit,
|
||||
exclusive_start_key, index_name)
|
||||
return results, scanned_count, last_evaluated_key
|
||||
@ -962,7 +970,7 @@ class DynamoDBBackend(BaseBackend):
|
||||
return table.query(hash_key, range_comparison, range_values, limit,
|
||||
exclusive_start_key, scan_index_forward, projection_expression, index_name, filter_expression, **filter_kwargs)
|
||||
|
||||
def scan(self, table_name, filters, limit, exclusive_start_key, filter_expression, expr_names, expr_values, index_name):
|
||||
def scan(self, table_name, filters, limit, exclusive_start_key, filter_expression, expr_names, expr_values, index_name, projection_expression):
|
||||
table = self.tables.get(table_name)
|
||||
if not table:
|
||||
return None, None, None
|
||||
@ -977,7 +985,9 @@ class DynamoDBBackend(BaseBackend):
|
||||
else:
|
||||
filter_expression = Op(None, None) # Will always eval to true
|
||||
|
||||
return table.scan(scan_filters, limit, exclusive_start_key, filter_expression, index_name)
|
||||
projection_expression = ','.join([expr_names.get(attr, attr) for attr in projection_expression.replace(' ', '').split(',')])
|
||||
|
||||
return table.scan(scan_filters, limit, exclusive_start_key, filter_expression, index_name, projection_expression)
|
||||
|
||||
def update_item(self, table_name, key, update_expression, attribute_updates, expression_attribute_names,
|
||||
expression_attribute_values, expected=None):
|
||||
|
@ -166,7 +166,7 @@ class DynamoHandler(BaseResponse):
|
||||
when BillingMode is PAY_PER_REQUEST')
|
||||
throughput = None
|
||||
else: # Provisioned (default billing mode)
|
||||
throughput = body["ProvisionedThroughput"]
|
||||
throughput = body.get("ProvisionedThroughput")
|
||||
# getting the schema
|
||||
key_schema = body['KeySchema']
|
||||
# getting attribute definition
|
||||
@ -558,7 +558,7 @@ class DynamoHandler(BaseResponse):
|
||||
filter_expression = self.body.get('FilterExpression')
|
||||
expression_attribute_values = self.body.get('ExpressionAttributeValues', {})
|
||||
expression_attribute_names = self.body.get('ExpressionAttributeNames', {})
|
||||
|
||||
projection_expression = self.body.get('ProjectionExpression', '')
|
||||
exclusive_start_key = self.body.get('ExclusiveStartKey')
|
||||
limit = self.body.get("Limit")
|
||||
index_name = self.body.get('IndexName')
|
||||
@ -570,7 +570,8 @@ class DynamoHandler(BaseResponse):
|
||||
filter_expression,
|
||||
expression_attribute_names,
|
||||
expression_attribute_values,
|
||||
index_name)
|
||||
index_name,
|
||||
projection_expression)
|
||||
except InvalidIndexNameError as err:
|
||||
er = 'com.amazonaws.dynamodb.v20111205#ValidationException'
|
||||
return self.error(er, str(err))
|
||||
|
@ -332,6 +332,15 @@ class InvalidParameterValueErrorTagNull(EC2ClientError):
|
||||
"Tag value cannot be null. Use empty string instead.")
|
||||
|
||||
|
||||
class InvalidParameterValueErrorUnknownAttribute(EC2ClientError):
|
||||
|
||||
def __init__(self, parameter_value):
|
||||
super(InvalidParameterValueErrorUnknownAttribute, self).__init__(
|
||||
"InvalidParameterValue",
|
||||
"Value ({0}) for parameter attribute is invalid. Unknown attribute."
|
||||
.format(parameter_value))
|
||||
|
||||
|
||||
class InvalidInternetGatewayIdError(EC2ClientError):
|
||||
|
||||
def __init__(self, internet_gateway_id):
|
||||
|
@ -54,6 +54,7 @@ from .exceptions import (
|
||||
InvalidNetworkInterfaceIdError,
|
||||
InvalidParameterValueError,
|
||||
InvalidParameterValueErrorTagNull,
|
||||
InvalidParameterValueErrorUnknownAttribute,
|
||||
InvalidPermissionNotFoundError,
|
||||
InvalidPermissionDuplicateError,
|
||||
InvalidRouteTableIdError,
|
||||
@ -383,6 +384,10 @@ class NetworkInterfaceBackend(object):
|
||||
|
||||
|
||||
class Instance(TaggedEC2Resource, BotoInstance):
|
||||
VALID_ATTRIBUTES = {'instanceType', 'kernel', 'ramdisk', 'userData', 'disableApiTermination',
|
||||
'instanceInitiatedShutdownBehavior', 'rootDeviceName', 'blockDeviceMapping',
|
||||
'productCodes', 'sourceDestCheck', 'groupSet', 'ebsOptimized', 'sriovNetSupport'}
|
||||
|
||||
def __init__(self, ec2_backend, image_id, user_data, security_groups, **kwargs):
|
||||
super(Instance, self).__init__()
|
||||
self.ec2_backend = ec2_backend
|
||||
@ -405,6 +410,8 @@ class Instance(TaggedEC2Resource, BotoInstance):
|
||||
self.launch_time = utc_date_and_time()
|
||||
self.ami_launch_index = kwargs.get("ami_launch_index", 0)
|
||||
self.disable_api_termination = kwargs.get("disable_api_termination", False)
|
||||
self.instance_initiated_shutdown_behavior = kwargs.get("instance_initiated_shutdown_behavior", "stop")
|
||||
self.sriov_net_support = "simple"
|
||||
self._spot_fleet_id = kwargs.get("spot_fleet_id", None)
|
||||
associate_public_ip = kwargs.get("associate_public_ip", False)
|
||||
if in_ec2_classic:
|
||||
@ -788,14 +795,22 @@ class InstanceBackend(object):
|
||||
setattr(instance, key, value)
|
||||
return instance
|
||||
|
||||
def modify_instance_security_groups(self, instance_id, new_group_list):
|
||||
def modify_instance_security_groups(self, instance_id, new_group_id_list):
|
||||
instance = self.get_instance(instance_id)
|
||||
new_group_list = []
|
||||
for new_group_id in new_group_id_list:
|
||||
new_group_list.append(self.get_security_group_from_id(new_group_id))
|
||||
setattr(instance, 'security_groups', new_group_list)
|
||||
return instance
|
||||
|
||||
def describe_instance_attribute(self, instance_id, key):
|
||||
if key == 'group_set':
|
||||
def describe_instance_attribute(self, instance_id, attribute):
|
||||
if attribute not in Instance.VALID_ATTRIBUTES:
|
||||
raise InvalidParameterValueErrorUnknownAttribute(attribute)
|
||||
|
||||
if attribute == 'groupSet':
|
||||
key = 'security_groups'
|
||||
else:
|
||||
key = camelcase_to_underscores(attribute)
|
||||
instance = self.get_instance(instance_id)
|
||||
value = getattr(instance, key)
|
||||
return instance, value
|
||||
|
@ -46,6 +46,7 @@ class InstanceResponse(BaseResponse):
|
||||
associate_public_ip = self._get_param('AssociatePublicIpAddress')
|
||||
key_name = self._get_param('KeyName')
|
||||
ebs_optimized = self._get_param('EbsOptimized')
|
||||
instance_initiated_shutdown_behavior = self._get_param("InstanceInitiatedShutdownBehavior")
|
||||
tags = self._parse_tag_specification("TagSpecification")
|
||||
region_name = self.region
|
||||
|
||||
@ -55,7 +56,7 @@ class InstanceResponse(BaseResponse):
|
||||
instance_type=instance_type, placement=placement, region_name=region_name, subnet_id=subnet_id,
|
||||
owner_id=owner_id, key_name=key_name, security_group_ids=security_group_ids,
|
||||
nics=nics, private_ip=private_ip, associate_public_ip=associate_public_ip,
|
||||
tags=tags, ebs_optimized=ebs_optimized)
|
||||
tags=tags, ebs_optimized=ebs_optimized, instance_initiated_shutdown_behavior=instance_initiated_shutdown_behavior)
|
||||
|
||||
template = self.response_template(EC2_RUN_INSTANCES)
|
||||
return template.render(reservation=new_reservation)
|
||||
@ -113,12 +114,11 @@ class InstanceResponse(BaseResponse):
|
||||
# TODO this and modify below should raise IncorrectInstanceState if
|
||||
# instance not in stopped state
|
||||
attribute = self._get_param('Attribute')
|
||||
key = camelcase_to_underscores(attribute)
|
||||
instance_id = self._get_param('InstanceId')
|
||||
instance, value = self.ec2_backend.describe_instance_attribute(
|
||||
instance_id, key)
|
||||
instance_id, attribute)
|
||||
|
||||
if key == "group_set":
|
||||
if attribute == "groupSet":
|
||||
template = self.response_template(
|
||||
EC2_DESCRIBE_INSTANCE_GROUPSET_ATTRIBUTE)
|
||||
else:
|
||||
@ -597,7 +597,9 @@ EC2_DESCRIBE_INSTANCE_ATTRIBUTE = """<DescribeInstanceAttributeResponse xmlns="h
|
||||
<requestId>59dbff89-35bd-4eac-99ed-be587EXAMPLE</requestId>
|
||||
<instanceId>{{ instance.id }}</instanceId>
|
||||
<{{ attribute }}>
|
||||
{% if value is not none %}
|
||||
<value>{{ value }}</value>
|
||||
{% endif %}
|
||||
</{{ attribute }}>
|
||||
</DescribeInstanceAttributeResponse>"""
|
||||
|
||||
@ -605,9 +607,9 @@ EC2_DESCRIBE_INSTANCE_GROUPSET_ATTRIBUTE = """<DescribeInstanceAttributeResponse
|
||||
<requestId>59dbff89-35bd-4eac-99ed-be587EXAMPLE</requestId>
|
||||
<instanceId>{{ instance.id }}</instanceId>
|
||||
<{{ attribute }}>
|
||||
{% for sg_id in value %}
|
||||
{% for sg in value %}
|
||||
<item>
|
||||
<groupId>{{ sg_id }}</groupId>
|
||||
<groupId>{{ sg.id }}</groupId>
|
||||
</item>
|
||||
{% endfor %}
|
||||
</{{ attribute }}>
|
||||
|
@ -3,6 +3,7 @@ from __future__ import unicode_literals
|
||||
import hashlib
|
||||
import re
|
||||
from copy import copy
|
||||
from datetime import datetime
|
||||
from random import random
|
||||
|
||||
from botocore.exceptions import ParamValidationError
|
||||
@ -106,7 +107,7 @@ class Image(BaseObject):
|
||||
self.repository = repository
|
||||
self.registry_id = registry_id
|
||||
self.image_digest = digest
|
||||
self.image_pushed_at = None
|
||||
self.image_pushed_at = str(datetime.utcnow().isoformat())
|
||||
|
||||
def _create_digest(self):
|
||||
image_contents = 'docker_image{0}'.format(int(random() * 10 ** 6))
|
||||
@ -158,7 +159,7 @@ class Image(BaseObject):
|
||||
response_object['repositoryName'] = self.repository
|
||||
response_object['registryId'] = self.registry_id
|
||||
response_object['imageSizeInBytes'] = self.image_size_in_bytes
|
||||
response_object['imagePushedAt'] = '2017-05-09'
|
||||
response_object['imagePushedAt'] = self.image_pushed_at
|
||||
return {k: v for k, v in response_object.items() if v is not None and v != []}
|
||||
|
||||
@property
|
||||
@ -402,7 +403,10 @@ class ECRBackend(BaseBackend):
|
||||
image_found = True
|
||||
repository.images[num].image_tag = image_id["imageTag"]
|
||||
response["imageIds"].append(image.response_batch_delete_image)
|
||||
repository.images[num].remove_tag(image_id["imageTag"])
|
||||
if len(image.image_tags) > 1:
|
||||
repository.images[num].remove_tag(image_id["imageTag"])
|
||||
else:
|
||||
repository.images.remove(image)
|
||||
|
||||
if not image_found:
|
||||
failure_response = {
|
||||
|
@ -138,6 +138,12 @@ class FakeTable(BaseModel):
|
||||
raise PartitionAlreadyExistsException()
|
||||
self.partitions[key] = partition
|
||||
|
||||
def delete_partition(self, values):
|
||||
try:
|
||||
del self.partitions[str(values)]
|
||||
except KeyError:
|
||||
raise PartitionNotFoundException()
|
||||
|
||||
|
||||
class FakePartition(BaseModel):
|
||||
def __init__(self, database_name, table_name, partiton_input):
|
||||
|
@ -4,6 +4,11 @@ import json
|
||||
|
||||
from moto.core.responses import BaseResponse
|
||||
from .models import glue_backend
|
||||
from .exceptions import (
|
||||
PartitionAlreadyExistsException,
|
||||
PartitionNotFoundException,
|
||||
TableNotFoundException
|
||||
)
|
||||
|
||||
|
||||
class GlueResponse(BaseResponse):
|
||||
@ -90,6 +95,28 @@ class GlueResponse(BaseResponse):
|
||||
resp = self.glue_backend.delete_table(database_name, table_name)
|
||||
return json.dumps(resp)
|
||||
|
||||
def batch_delete_table(self):
|
||||
database_name = self.parameters.get('DatabaseName')
|
||||
|
||||
errors = []
|
||||
for table_name in self.parameters.get('TablesToDelete'):
|
||||
try:
|
||||
self.glue_backend.delete_table(database_name, table_name)
|
||||
except TableNotFoundException:
|
||||
errors.append({
|
||||
"TableName": table_name,
|
||||
"ErrorDetail": {
|
||||
"ErrorCode": "EntityNotFoundException",
|
||||
"ErrorMessage": "Table not found"
|
||||
}
|
||||
})
|
||||
|
||||
out = {}
|
||||
if errors:
|
||||
out["Errors"] = errors
|
||||
|
||||
return json.dumps(out)
|
||||
|
||||
def get_partitions(self):
|
||||
database_name = self.parameters.get('DatabaseName')
|
||||
table_name = self.parameters.get('TableName')
|
||||
@ -124,6 +151,30 @@ class GlueResponse(BaseResponse):
|
||||
|
||||
return ""
|
||||
|
||||
def batch_create_partition(self):
|
||||
database_name = self.parameters.get('DatabaseName')
|
||||
table_name = self.parameters.get('TableName')
|
||||
table = self.glue_backend.get_table(database_name, table_name)
|
||||
|
||||
errors_output = []
|
||||
for part_input in self.parameters.get('PartitionInputList'):
|
||||
try:
|
||||
table.create_partition(part_input)
|
||||
except PartitionAlreadyExistsException:
|
||||
errors_output.append({
|
||||
'PartitionValues': part_input['Values'],
|
||||
'ErrorDetail': {
|
||||
'ErrorCode': 'AlreadyExistsException',
|
||||
'ErrorMessage': 'Partition already exists.'
|
||||
}
|
||||
})
|
||||
|
||||
out = {}
|
||||
if errors_output:
|
||||
out["Errors"] = errors_output
|
||||
|
||||
return json.dumps(out)
|
||||
|
||||
def update_partition(self):
|
||||
database_name = self.parameters.get('DatabaseName')
|
||||
table_name = self.parameters.get('TableName')
|
||||
@ -134,3 +185,38 @@ class GlueResponse(BaseResponse):
|
||||
table.update_partition(part_to_update, part_input)
|
||||
|
||||
return ""
|
||||
|
||||
def delete_partition(self):
|
||||
database_name = self.parameters.get('DatabaseName')
|
||||
table_name = self.parameters.get('TableName')
|
||||
part_to_delete = self.parameters.get('PartitionValues')
|
||||
|
||||
table = self.glue_backend.get_table(database_name, table_name)
|
||||
table.delete_partition(part_to_delete)
|
||||
|
||||
return ""
|
||||
|
||||
def batch_delete_partition(self):
|
||||
database_name = self.parameters.get('DatabaseName')
|
||||
table_name = self.parameters.get('TableName')
|
||||
table = self.glue_backend.get_table(database_name, table_name)
|
||||
|
||||
errors_output = []
|
||||
for part_input in self.parameters.get('PartitionsToDelete'):
|
||||
values = part_input.get('Values')
|
||||
try:
|
||||
table.delete_partition(values)
|
||||
except PartitionNotFoundException:
|
||||
errors_output.append({
|
||||
'PartitionValues': values,
|
||||
'ErrorDetail': {
|
||||
'ErrorCode': 'EntityNotFoundException',
|
||||
'ErrorMessage': 'Partition not found',
|
||||
}
|
||||
})
|
||||
|
||||
out = {}
|
||||
if errors_output:
|
||||
out['Errors'] = errors_output
|
||||
|
||||
return json.dumps(out)
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -26,6 +26,14 @@ class IAMReportNotPresentException(RESTError):
|
||||
"ReportNotPresent", message)
|
||||
|
||||
|
||||
class IAMLimitExceededException(RESTError):
|
||||
code = 400
|
||||
|
||||
def __init__(self, message):
|
||||
super(IAMLimitExceededException, self).__init__(
|
||||
"LimitExceeded", message)
|
||||
|
||||
|
||||
class MalformedCertificate(RESTError):
|
||||
code = 400
|
||||
|
||||
@ -34,6 +42,14 @@ class MalformedCertificate(RESTError):
|
||||
'MalformedCertificate', 'Certificate {cert} is malformed'.format(cert=cert))
|
||||
|
||||
|
||||
class MalformedPolicyDocument(RESTError):
|
||||
code = 400
|
||||
|
||||
def __init__(self, message=""):
|
||||
super(MalformedPolicyDocument, self).__init__(
|
||||
'MalformedPolicyDocument', message)
|
||||
|
||||
|
||||
class DuplicateTags(RESTError):
|
||||
code = 400
|
||||
|
||||
|
@ -8,14 +8,14 @@ import re
|
||||
from cryptography import x509
|
||||
from cryptography.hazmat.backends import default_backend
|
||||
|
||||
import pytz
|
||||
from moto.core.exceptions import RESTError
|
||||
from moto.core import BaseBackend, BaseModel
|
||||
from moto.core.utils import iso_8601_datetime_without_milliseconds
|
||||
from moto.core.utils import iso_8601_datetime_without_milliseconds, iso_8601_datetime_with_milliseconds
|
||||
from moto.iam.policy_validation import IAMPolicyDocumentValidator
|
||||
|
||||
from .aws_managed_policies import aws_managed_policies_data
|
||||
from .exceptions import IAMNotFoundException, IAMConflictException, IAMReportNotPresentException, MalformedCertificate, \
|
||||
DuplicateTags, TagKeyTooBig, InvalidTagCharacters, TooManyTags, TagValueTooBig
|
||||
from .exceptions import IAMNotFoundException, IAMConflictException, IAMReportNotPresentException, IAMLimitExceededException, \
|
||||
MalformedCertificate, DuplicateTags, TagKeyTooBig, InvalidTagCharacters, TooManyTags, TagValueTooBig
|
||||
from .utils import random_access_key, random_alphanumeric, random_resource_id, random_policy_id
|
||||
|
||||
ACCOUNT_ID = 123456789012
|
||||
@ -28,11 +28,15 @@ class MFADevice(object):
|
||||
serial_number,
|
||||
authentication_code_1,
|
||||
authentication_code_2):
|
||||
self.enable_date = datetime.now(pytz.utc)
|
||||
self.enable_date = datetime.utcnow()
|
||||
self.serial_number = serial_number
|
||||
self.authentication_code_1 = authentication_code_1
|
||||
self.authentication_code_2 = authentication_code_2
|
||||
|
||||
@property
|
||||
def enabled_iso_8601(self):
|
||||
return iso_8601_datetime_without_milliseconds(self.enable_date)
|
||||
|
||||
|
||||
class Policy(BaseModel):
|
||||
is_attachable = False
|
||||
@ -42,7 +46,9 @@ class Policy(BaseModel):
|
||||
default_version_id=None,
|
||||
description=None,
|
||||
document=None,
|
||||
path=None):
|
||||
path=None,
|
||||
create_date=None,
|
||||
update_date=None):
|
||||
self.name = name
|
||||
|
||||
self.attachment_count = 0
|
||||
@ -56,10 +62,25 @@ class Policy(BaseModel):
|
||||
else:
|
||||
self.default_version_id = 'v1'
|
||||
self.next_version_num = 2
|
||||
self.versions = [PolicyVersion(self.arn, document, True)]
|
||||
self.versions = [PolicyVersion(self.arn, document, True, self.default_version_id, update_date)]
|
||||
|
||||
self.create_datetime = datetime.now(pytz.utc)
|
||||
self.update_datetime = datetime.now(pytz.utc)
|
||||
self.create_date = create_date if create_date is not None else datetime.utcnow()
|
||||
self.update_date = update_date if update_date is not None else datetime.utcnow()
|
||||
|
||||
def update_default_version(self, new_default_version_id):
|
||||
for version in self.versions:
|
||||
if version.version_id == self.default_version_id:
|
||||
version.is_default = False
|
||||
break
|
||||
self.default_version_id = new_default_version_id
|
||||
|
||||
@property
|
||||
def created_iso_8601(self):
|
||||
return iso_8601_datetime_with_milliseconds(self.create_date)
|
||||
|
||||
@property
|
||||
def updated_iso_8601(self):
|
||||
return iso_8601_datetime_with_milliseconds(self.update_date)
|
||||
|
||||
|
||||
class SAMLProvider(BaseModel):
|
||||
@ -77,13 +98,19 @@ class PolicyVersion(object):
|
||||
def __init__(self,
|
||||
policy_arn,
|
||||
document,
|
||||
is_default=False):
|
||||
is_default=False,
|
||||
version_id='v1',
|
||||
create_date=None):
|
||||
self.policy_arn = policy_arn
|
||||
self.document = document or {}
|
||||
self.is_default = is_default
|
||||
self.version_id = 'v1'
|
||||
self.version_id = version_id
|
||||
|
||||
self.create_datetime = datetime.now(pytz.utc)
|
||||
self.create_date = create_date if create_date is not None else datetime.utcnow()
|
||||
|
||||
@property
|
||||
def created_iso_8601(self):
|
||||
return iso_8601_datetime_with_milliseconds(self.create_date)
|
||||
|
||||
|
||||
class ManagedPolicy(Policy):
|
||||
@ -112,7 +139,9 @@ class AWSManagedPolicy(ManagedPolicy):
|
||||
return cls(name,
|
||||
default_version_id=data.get('DefaultVersionId'),
|
||||
path=data.get('Path'),
|
||||
document=data.get('Document'))
|
||||
document=json.dumps(data.get('Document')),
|
||||
create_date=datetime.strptime(data.get('CreateDate'), "%Y-%m-%dT%H:%M:%S+00:00"),
|
||||
update_date=datetime.strptime(data.get('UpdateDate'), "%Y-%m-%dT%H:%M:%S+00:00"))
|
||||
|
||||
@property
|
||||
def arn(self):
|
||||
@ -139,11 +168,15 @@ class Role(BaseModel):
|
||||
self.path = path or '/'
|
||||
self.policies = {}
|
||||
self.managed_policies = {}
|
||||
self.create_date = datetime.now(pytz.utc)
|
||||
self.create_date = datetime.utcnow()
|
||||
self.tags = {}
|
||||
self.description = ""
|
||||
self.permissions_boundary = permissions_boundary
|
||||
|
||||
@property
|
||||
def created_iso_8601(self):
|
||||
return iso_8601_datetime_with_milliseconds(self.create_date)
|
||||
|
||||
@classmethod
|
||||
def create_from_cloudformation_json(cls, resource_name, cloudformation_json, region_name):
|
||||
properties = cloudformation_json['Properties']
|
||||
@ -198,7 +231,11 @@ class InstanceProfile(BaseModel):
|
||||
self.name = name
|
||||
self.path = path or '/'
|
||||
self.roles = roles if roles else []
|
||||
self.create_date = datetime.now(pytz.utc)
|
||||
self.create_date = datetime.utcnow()
|
||||
|
||||
@property
|
||||
def created_iso_8601(self):
|
||||
return iso_8601_datetime_with_milliseconds(self.create_date)
|
||||
|
||||
@classmethod
|
||||
def create_from_cloudformation_json(cls, resource_name, cloudformation_json, region_name):
|
||||
@ -250,25 +287,31 @@ class SigningCertificate(BaseModel):
|
||||
self.id = id
|
||||
self.user_name = user_name
|
||||
self.body = body
|
||||
self.upload_date = datetime.strftime(datetime.utcnow(), "%Y-%m-%d-%H-%M-%S")
|
||||
self.upload_date = datetime.utcnow()
|
||||
self.status = 'Active'
|
||||
|
||||
@property
|
||||
def uploaded_iso_8601(self):
|
||||
return iso_8601_datetime_without_milliseconds(self.upload_date)
|
||||
|
||||
|
||||
class AccessKey(BaseModel):
|
||||
|
||||
def __init__(self, user_name):
|
||||
self.user_name = user_name
|
||||
self.access_key_id = random_access_key()
|
||||
self.secret_access_key = random_alphanumeric(32)
|
||||
self.access_key_id = "AKIA" + random_access_key()
|
||||
self.secret_access_key = random_alphanumeric(40)
|
||||
self.status = 'Active'
|
||||
self.create_date = datetime.strftime(
|
||||
datetime.utcnow(),
|
||||
"%Y-%m-%dT%H:%M:%SZ"
|
||||
)
|
||||
self.last_used = datetime.strftime(
|
||||
datetime.utcnow(),
|
||||
"%Y-%m-%dT%H:%M:%SZ"
|
||||
)
|
||||
self.create_date = datetime.utcnow()
|
||||
self.last_used = datetime.utcnow()
|
||||
|
||||
@property
|
||||
def created_iso_8601(self):
|
||||
return iso_8601_datetime_without_milliseconds(self.create_date)
|
||||
|
||||
@property
|
||||
def last_used_iso_8601(self):
|
||||
return iso_8601_datetime_without_milliseconds(self.last_used)
|
||||
|
||||
def get_cfn_attribute(self, attribute_name):
|
||||
from moto.cloudformation.exceptions import UnformattedGetAttTemplateException
|
||||
@ -283,15 +326,16 @@ class Group(BaseModel):
|
||||
self.name = name
|
||||
self.id = random_resource_id()
|
||||
self.path = path
|
||||
self.created = datetime.strftime(
|
||||
datetime.utcnow(),
|
||||
"%Y-%m-%d-%H-%M-%S"
|
||||
)
|
||||
self.create_date = datetime.utcnow()
|
||||
|
||||
self.users = []
|
||||
self.managed_policies = {}
|
||||
self.policies = {}
|
||||
|
||||
@property
|
||||
def created_iso_8601(self):
|
||||
return iso_8601_datetime_with_milliseconds(self.create_date)
|
||||
|
||||
def get_cfn_attribute(self, attribute_name):
|
||||
from moto.cloudformation.exceptions import UnformattedGetAttTemplateException
|
||||
if attribute_name == 'Arn':
|
||||
@ -306,10 +350,6 @@ class Group(BaseModel):
|
||||
else:
|
||||
return "arn:aws:iam::{0}:group/{1}/{2}".format(ACCOUNT_ID, self.path, self.name)
|
||||
|
||||
@property
|
||||
def create_date(self):
|
||||
return self.created
|
||||
|
||||
def get_policy(self, policy_name):
|
||||
try:
|
||||
policy_json = self.policies[policy_name]
|
||||
@ -335,7 +375,7 @@ class User(BaseModel):
|
||||
self.name = name
|
||||
self.id = random_resource_id()
|
||||
self.path = path if path else "/"
|
||||
self.created = datetime.utcnow()
|
||||
self.create_date = datetime.utcnow()
|
||||
self.mfa_devices = {}
|
||||
self.policies = {}
|
||||
self.managed_policies = {}
|
||||
@ -350,7 +390,7 @@ class User(BaseModel):
|
||||
|
||||
@property
|
||||
def created_iso_8601(self):
|
||||
return iso_8601_datetime_without_milliseconds(self.created)
|
||||
return iso_8601_datetime_with_milliseconds(self.create_date)
|
||||
|
||||
def get_policy(self, policy_name):
|
||||
policy_json = None
|
||||
@ -421,7 +461,7 @@ class User(BaseModel):
|
||||
|
||||
def to_csv(self):
|
||||
date_format = '%Y-%m-%dT%H:%M:%S+00:00'
|
||||
date_created = self.created
|
||||
date_created = self.create_date
|
||||
# aagrawal,arn:aws:iam::509284790694:user/aagrawal,2014-09-01T22:28:48+00:00,true,2014-11-12T23:36:49+00:00,2014-09-03T18:59:00+00:00,N/A,false,true,2014-09-01T22:28:48+00:00,false,N/A,false,N/A,false,N/A
|
||||
if not self.password:
|
||||
password_enabled = 'false'
|
||||
@ -478,7 +518,7 @@ class IAMBackend(BaseBackend):
|
||||
super(IAMBackend, self).__init__()
|
||||
|
||||
def _init_managed_policies(self):
|
||||
return dict((p.name, p) for p in aws_managed_policies)
|
||||
return dict((p.arn, p) for p in aws_managed_policies)
|
||||
|
||||
def attach_role_policy(self, policy_arn, role_name):
|
||||
arns = dict((p.arn, p) for p in self.managed_policies.values())
|
||||
@ -536,6 +576,9 @@ class IAMBackend(BaseBackend):
|
||||
policy.detach_from(self.get_user(user_name))
|
||||
|
||||
def create_policy(self, description, path, policy_document, policy_name):
|
||||
iam_policy_document_validator = IAMPolicyDocumentValidator(policy_document)
|
||||
iam_policy_document_validator.validate()
|
||||
|
||||
policy = ManagedPolicy(
|
||||
policy_name,
|
||||
description=description,
|
||||
@ -628,6 +671,9 @@ class IAMBackend(BaseBackend):
|
||||
|
||||
def put_role_policy(self, role_name, policy_name, policy_json):
|
||||
role = self.get_role(role_name)
|
||||
|
||||
iam_policy_document_validator = IAMPolicyDocumentValidator(policy_json)
|
||||
iam_policy_document_validator.validate()
|
||||
role.put_policy(policy_name, policy_json)
|
||||
|
||||
def delete_role_policy(self, role_name, policy_name):
|
||||
@ -639,6 +685,7 @@ class IAMBackend(BaseBackend):
|
||||
for p, d in role.policies.items():
|
||||
if p == policy_name:
|
||||
return p, d
|
||||
raise IAMNotFoundException("Policy Document {0} not attached to role {1}".format(policy_name, role_name))
|
||||
|
||||
def list_role_policies(self, role_name):
|
||||
role = self.get_role(role_name)
|
||||
@ -725,15 +772,21 @@ class IAMBackend(BaseBackend):
|
||||
role.tags.pop(ref_key, None)
|
||||
|
||||
def create_policy_version(self, policy_arn, policy_document, set_as_default):
|
||||
iam_policy_document_validator = IAMPolicyDocumentValidator(policy_document)
|
||||
iam_policy_document_validator.validate()
|
||||
|
||||
policy = self.get_policy(policy_arn)
|
||||
if not policy:
|
||||
raise IAMNotFoundException("Policy not found")
|
||||
if len(policy.versions) >= 5:
|
||||
raise IAMLimitExceededException("A managed policy can have up to 5 versions. Before you create a new version, you must delete an existing version.")
|
||||
set_as_default = (set_as_default == "true") # convert it to python bool
|
||||
version = PolicyVersion(policy_arn, policy_document, set_as_default)
|
||||
policy.versions.append(version)
|
||||
version.version_id = 'v{0}'.format(policy.next_version_num)
|
||||
policy.next_version_num += 1
|
||||
if set_as_default:
|
||||
policy.default_version_id = version.version_id
|
||||
policy.update_default_version(version.version_id)
|
||||
return version
|
||||
|
||||
def get_policy_version(self, policy_arn, version_id):
|
||||
@ -756,8 +809,8 @@ class IAMBackend(BaseBackend):
|
||||
if not policy:
|
||||
raise IAMNotFoundException("Policy not found")
|
||||
if version_id == policy.default_version_id:
|
||||
raise IAMConflictException(
|
||||
"Cannot delete the default version of a policy")
|
||||
raise IAMConflictException(code="DeleteConflict",
|
||||
message="Cannot delete the default version of a policy.")
|
||||
for i, v in enumerate(policy.versions):
|
||||
if v.version_id == version_id:
|
||||
del policy.versions[i]
|
||||
@ -869,6 +922,9 @@ class IAMBackend(BaseBackend):
|
||||
|
||||
def put_group_policy(self, group_name, policy_name, policy_json):
|
||||
group = self.get_group(group_name)
|
||||
|
||||
iam_policy_document_validator = IAMPolicyDocumentValidator(policy_json)
|
||||
iam_policy_document_validator.validate()
|
||||
group.put_policy(policy_name, policy_json)
|
||||
|
||||
def list_group_policies(self, group_name, marker=None, max_items=None):
|
||||
@ -1029,6 +1085,9 @@ class IAMBackend(BaseBackend):
|
||||
|
||||
def put_user_policy(self, user_name, policy_name, policy_json):
|
||||
user = self.get_user(user_name)
|
||||
|
||||
iam_policy_document_validator = IAMPolicyDocumentValidator(policy_json)
|
||||
iam_policy_document_validator.validate()
|
||||
user.put_policy(policy_name, policy_json)
|
||||
|
||||
def delete_user_policy(self, user_name, policy_name):
|
||||
@ -1050,7 +1109,7 @@ class IAMBackend(BaseBackend):
|
||||
if key.access_key_id == access_key_id:
|
||||
return {
|
||||
'user_name': key.user_name,
|
||||
'last_used': key.last_used
|
||||
'last_used': key.last_used_iso_8601,
|
||||
}
|
||||
else:
|
||||
raise IAMNotFoundException(
|
||||
|
450
moto/iam/policy_validation.py
Normal file
450
moto/iam/policy_validation.py
Normal file
@ -0,0 +1,450 @@
|
||||
import json
|
||||
import re
|
||||
|
||||
from six import string_types
|
||||
|
||||
from moto.iam.exceptions import MalformedPolicyDocument
|
||||
|
||||
|
||||
VALID_TOP_ELEMENTS = [
|
||||
"Version",
|
||||
"Id",
|
||||
"Statement",
|
||||
"Conditions"
|
||||
]
|
||||
|
||||
VALID_VERSIONS = [
|
||||
"2008-10-17",
|
||||
"2012-10-17"
|
||||
]
|
||||
|
||||
VALID_STATEMENT_ELEMENTS = [
|
||||
"Sid",
|
||||
"Action",
|
||||
"NotAction",
|
||||
"Resource",
|
||||
"NotResource",
|
||||
"Effect",
|
||||
"Condition"
|
||||
]
|
||||
|
||||
VALID_EFFECTS = [
|
||||
"Allow",
|
||||
"Deny"
|
||||
]
|
||||
|
||||
VALID_CONDITIONS = [
|
||||
"StringEquals",
|
||||
"StringNotEquals",
|
||||
"StringEqualsIgnoreCase",
|
||||
"StringNotEqualsIgnoreCase",
|
||||
"StringLike",
|
||||
"StringNotLike",
|
||||
"NumericEquals",
|
||||
"NumericNotEquals",
|
||||
"NumericLessThan",
|
||||
"NumericLessThanEquals",
|
||||
"NumericGreaterThan",
|
||||
"NumericGreaterThanEquals",
|
||||
"DateEquals",
|
||||
"DateNotEquals",
|
||||
"DateLessThan",
|
||||
"DateLessThanEquals",
|
||||
"DateGreaterThan",
|
||||
"DateGreaterThanEquals",
|
||||
"Bool",
|
||||
"BinaryEquals",
|
||||
"IpAddress",
|
||||
"NotIpAddress",
|
||||
"ArnEquals",
|
||||
"ArnLike",
|
||||
"ArnNotEquals",
|
||||
"ArnNotLike",
|
||||
"Null"
|
||||
]
|
||||
|
||||
VALID_CONDITION_PREFIXES = [
|
||||
"ForAnyValue:",
|
||||
"ForAllValues:"
|
||||
]
|
||||
|
||||
VALID_CONDITION_POSTFIXES = [
|
||||
"IfExists"
|
||||
]
|
||||
|
||||
SERVICE_TYPE_REGION_INFORMATION_ERROR_ASSOCIATIONS = {
|
||||
"iam": 'IAM resource {resource} cannot contain region information.',
|
||||
"s3": 'Resource {resource} can not contain region information.'
|
||||
}
|
||||
|
||||
VALID_RESOURCE_PATH_STARTING_VALUES = {
|
||||
"iam": {
|
||||
"values": ["user/", "federated-user/", "role/", "group/", "instance-profile/", "mfa/", "server-certificate/",
|
||||
"policy/", "sms-mfa/", "saml-provider/", "oidc-provider/", "report/", "access-report/"],
|
||||
"error_message": 'IAM resource path must either be "*" or start with {values}.'
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class IAMPolicyDocumentValidator:
|
||||
|
||||
def __init__(self, policy_document):
|
||||
self._policy_document = policy_document
|
||||
self._policy_json = {}
|
||||
self._statements = []
|
||||
self._resource_error = "" # the first resource error found that does not generate a legacy parsing error
|
||||
|
||||
def validate(self):
|
||||
try:
|
||||
self._validate_syntax()
|
||||
except Exception:
|
||||
raise MalformedPolicyDocument("Syntax errors in policy.")
|
||||
try:
|
||||
self._validate_version()
|
||||
except Exception:
|
||||
raise MalformedPolicyDocument("Policy document must be version 2012-10-17 or greater.")
|
||||
try:
|
||||
self._perform_first_legacy_parsing()
|
||||
self._validate_resources_for_formats()
|
||||
self._validate_not_resources_for_formats()
|
||||
except Exception:
|
||||
raise MalformedPolicyDocument("The policy failed legacy parsing")
|
||||
try:
|
||||
self._validate_sid_uniqueness()
|
||||
except Exception:
|
||||
raise MalformedPolicyDocument("Statement IDs (SID) in a single policy must be unique.")
|
||||
try:
|
||||
self._validate_action_like_exist()
|
||||
except Exception:
|
||||
raise MalformedPolicyDocument("Policy statement must contain actions.")
|
||||
try:
|
||||
self._validate_resource_exist()
|
||||
except Exception:
|
||||
raise MalformedPolicyDocument("Policy statement must contain resources.")
|
||||
|
||||
if self._resource_error != "":
|
||||
raise MalformedPolicyDocument(self._resource_error)
|
||||
|
||||
self._validate_actions_for_prefixes()
|
||||
self._validate_not_actions_for_prefixes()
|
||||
|
||||
def _validate_syntax(self):
|
||||
self._policy_json = json.loads(self._policy_document)
|
||||
assert isinstance(self._policy_json, dict)
|
||||
self._validate_top_elements()
|
||||
self._validate_version_syntax()
|
||||
self._validate_id_syntax()
|
||||
self._validate_statements_syntax()
|
||||
|
||||
def _validate_top_elements(self):
|
||||
top_elements = self._policy_json.keys()
|
||||
for element in top_elements:
|
||||
assert element in VALID_TOP_ELEMENTS
|
||||
|
||||
def _validate_version_syntax(self):
|
||||
if "Version" in self._policy_json:
|
||||
assert self._policy_json["Version"] in VALID_VERSIONS
|
||||
|
||||
def _validate_version(self):
|
||||
assert self._policy_json["Version"] == "2012-10-17"
|
||||
|
||||
def _validate_sid_uniqueness(self):
|
||||
sids = []
|
||||
for statement in self._statements:
|
||||
if "Sid" in statement:
|
||||
assert statement["Sid"] not in sids
|
||||
sids.append(statement["Sid"])
|
||||
|
||||
def _validate_statements_syntax(self):
|
||||
assert "Statement" in self._policy_json
|
||||
assert isinstance(self._policy_json["Statement"], (dict, list))
|
||||
|
||||
if isinstance(self._policy_json["Statement"], dict):
|
||||
self._statements.append(self._policy_json["Statement"])
|
||||
else:
|
||||
self._statements += self._policy_json["Statement"]
|
||||
|
||||
assert self._statements
|
||||
for statement in self._statements:
|
||||
self._validate_statement_syntax(statement)
|
||||
|
||||
@staticmethod
|
||||
def _validate_statement_syntax(statement):
|
||||
assert isinstance(statement, dict)
|
||||
for statement_element in statement.keys():
|
||||
assert statement_element in VALID_STATEMENT_ELEMENTS
|
||||
|
||||
assert ("Resource" not in statement or "NotResource" not in statement)
|
||||
assert ("Action" not in statement or "NotAction" not in statement)
|
||||
|
||||
IAMPolicyDocumentValidator._validate_effect_syntax(statement)
|
||||
IAMPolicyDocumentValidator._validate_action_syntax(statement)
|
||||
IAMPolicyDocumentValidator._validate_not_action_syntax(statement)
|
||||
IAMPolicyDocumentValidator._validate_resource_syntax(statement)
|
||||
IAMPolicyDocumentValidator._validate_not_resource_syntax(statement)
|
||||
IAMPolicyDocumentValidator._validate_condition_syntax(statement)
|
||||
IAMPolicyDocumentValidator._validate_sid_syntax(statement)
|
||||
|
||||
@staticmethod
|
||||
def _validate_effect_syntax(statement):
|
||||
assert "Effect" in statement
|
||||
assert isinstance(statement["Effect"], string_types)
|
||||
assert statement["Effect"].lower() in [allowed_effect.lower() for allowed_effect in VALID_EFFECTS]
|
||||
|
||||
@staticmethod
|
||||
def _validate_action_syntax(statement):
|
||||
IAMPolicyDocumentValidator._validate_string_or_list_of_strings_syntax(statement, "Action")
|
||||
|
||||
@staticmethod
|
||||
def _validate_not_action_syntax(statement):
|
||||
IAMPolicyDocumentValidator._validate_string_or_list_of_strings_syntax(statement, "NotAction")
|
||||
|
||||
@staticmethod
|
||||
def _validate_resource_syntax(statement):
|
||||
IAMPolicyDocumentValidator._validate_string_or_list_of_strings_syntax(statement, "Resource")
|
||||
|
||||
@staticmethod
|
||||
def _validate_not_resource_syntax(statement):
|
||||
IAMPolicyDocumentValidator._validate_string_or_list_of_strings_syntax(statement, "NotResource")
|
||||
|
||||
@staticmethod
|
||||
def _validate_string_or_list_of_strings_syntax(statement, key):
|
||||
if key in statement:
|
||||
assert isinstance(statement[key], (string_types, list))
|
||||
if isinstance(statement[key], list):
|
||||
for resource in statement[key]:
|
||||
assert isinstance(resource, string_types)
|
||||
|
||||
@staticmethod
|
||||
def _validate_condition_syntax(statement):
|
||||
if "Condition" in statement:
|
||||
assert isinstance(statement["Condition"], dict)
|
||||
for condition_key, condition_value in statement["Condition"].items():
|
||||
assert isinstance(condition_value, dict)
|
||||
for condition_element_key, condition_element_value in condition_value.items():
|
||||
assert isinstance(condition_element_value, (list, string_types))
|
||||
|
||||
if IAMPolicyDocumentValidator._strip_condition_key(condition_key) not in VALID_CONDITIONS:
|
||||
assert not condition_value # empty dict
|
||||
|
||||
@staticmethod
|
||||
def _strip_condition_key(condition_key):
|
||||
for valid_prefix in VALID_CONDITION_PREFIXES:
|
||||
if condition_key.startswith(valid_prefix):
|
||||
condition_key = condition_key[len(valid_prefix):]
|
||||
break # strip only the first match
|
||||
|
||||
for valid_postfix in VALID_CONDITION_POSTFIXES:
|
||||
if condition_key.endswith(valid_postfix):
|
||||
condition_key = condition_key[:-len(valid_postfix)]
|
||||
break # strip only the first match
|
||||
|
||||
return condition_key
|
||||
|
||||
@staticmethod
|
||||
def _validate_sid_syntax(statement):
|
||||
if "Sid" in statement:
|
||||
assert isinstance(statement["Sid"], string_types)
|
||||
|
||||
def _validate_id_syntax(self):
|
||||
if "Id" in self._policy_json:
|
||||
assert isinstance(self._policy_json["Id"], string_types)
|
||||
|
||||
def _validate_resource_exist(self):
|
||||
for statement in self._statements:
|
||||
assert ("Resource" in statement or "NotResource" in statement)
|
||||
if "Resource" in statement and isinstance(statement["Resource"], list):
|
||||
assert statement["Resource"]
|
||||
elif "NotResource" in statement and isinstance(statement["NotResource"], list):
|
||||
assert statement["NotResource"]
|
||||
|
||||
def _validate_action_like_exist(self):
|
||||
for statement in self._statements:
|
||||
assert ("Action" in statement or "NotAction" in statement)
|
||||
if "Action" in statement and isinstance(statement["Action"], list):
|
||||
assert statement["Action"]
|
||||
elif "NotAction" in statement and isinstance(statement["NotAction"], list):
|
||||
assert statement["NotAction"]
|
||||
|
||||
def _validate_actions_for_prefixes(self):
|
||||
self._validate_action_like_for_prefixes("Action")
|
||||
|
||||
def _validate_not_actions_for_prefixes(self):
|
||||
self._validate_action_like_for_prefixes("NotAction")
|
||||
|
||||
def _validate_action_like_for_prefixes(self, key):
|
||||
for statement in self._statements:
|
||||
if key in statement:
|
||||
if isinstance(statement[key], string_types):
|
||||
self._validate_action_prefix(statement[key])
|
||||
else:
|
||||
for action in statement[key]:
|
||||
self._validate_action_prefix(action)
|
||||
|
||||
@staticmethod
|
||||
def _validate_action_prefix(action):
|
||||
action_parts = action.split(":")
|
||||
if len(action_parts) == 1 and action_parts[0] != "*":
|
||||
raise MalformedPolicyDocument("Actions/Conditions must be prefaced by a vendor, e.g., iam, sdb, ec2, etc.")
|
||||
elif len(action_parts) > 2:
|
||||
raise MalformedPolicyDocument("Actions/Condition can contain only one colon.")
|
||||
|
||||
vendor_pattern = re.compile(r'[^a-zA-Z0-9\-.]')
|
||||
if action_parts[0] != "*" and vendor_pattern.search(action_parts[0]):
|
||||
raise MalformedPolicyDocument("Vendor {vendor} is not valid".format(vendor=action_parts[0]))
|
||||
|
||||
def _validate_resources_for_formats(self):
|
||||
self._validate_resource_like_for_formats("Resource")
|
||||
|
||||
def _validate_not_resources_for_formats(self):
|
||||
self._validate_resource_like_for_formats("NotResource")
|
||||
|
||||
def _validate_resource_like_for_formats(self, key):
|
||||
for statement in self._statements:
|
||||
if key in statement:
|
||||
if isinstance(statement[key], string_types):
|
||||
self._validate_resource_format(statement[key])
|
||||
else:
|
||||
for resource in sorted(statement[key], reverse=True):
|
||||
self._validate_resource_format(resource)
|
||||
if self._resource_error == "":
|
||||
IAMPolicyDocumentValidator._legacy_parse_resource_like(statement, key)
|
||||
|
||||
def _validate_resource_format(self, resource):
|
||||
if resource != "*":
|
||||
resource_partitions = resource.partition(":")
|
||||
|
||||
if resource_partitions[1] == "":
|
||||
self._resource_error = 'Resource {resource} must be in ARN format or "*".'.format(resource=resource)
|
||||
return
|
||||
|
||||
resource_partitions = resource_partitions[2].partition(":")
|
||||
if resource_partitions[0] != "aws":
|
||||
remaining_resource_parts = resource_partitions[2].split(":")
|
||||
|
||||
arn1 = remaining_resource_parts[0] if remaining_resource_parts[0] != "" or len(remaining_resource_parts) > 1 else "*"
|
||||
arn2 = remaining_resource_parts[1] if len(remaining_resource_parts) > 1 else "*"
|
||||
arn3 = remaining_resource_parts[2] if len(remaining_resource_parts) > 2 else "*"
|
||||
arn4 = ":".join(remaining_resource_parts[3:]) if len(remaining_resource_parts) > 3 else "*"
|
||||
self._resource_error = 'Partition "{partition}" is not valid for resource "arn:{partition}:{arn1}:{arn2}:{arn3}:{arn4}".'.format(
|
||||
partition=resource_partitions[0],
|
||||
arn1=arn1,
|
||||
arn2=arn2,
|
||||
arn3=arn3,
|
||||
arn4=arn4
|
||||
)
|
||||
return
|
||||
|
||||
if resource_partitions[1] != ":":
|
||||
self._resource_error = "Resource vendor must be fully qualified and cannot contain regexes."
|
||||
return
|
||||
|
||||
resource_partitions = resource_partitions[2].partition(":")
|
||||
|
||||
service = resource_partitions[0]
|
||||
|
||||
if service in SERVICE_TYPE_REGION_INFORMATION_ERROR_ASSOCIATIONS.keys() and not resource_partitions[2].startswith(":"):
|
||||
self._resource_error = SERVICE_TYPE_REGION_INFORMATION_ERROR_ASSOCIATIONS[service].format(resource=resource)
|
||||
return
|
||||
|
||||
resource_partitions = resource_partitions[2].partition(":")
|
||||
resource_partitions = resource_partitions[2].partition(":")
|
||||
|
||||
if service in VALID_RESOURCE_PATH_STARTING_VALUES.keys():
|
||||
valid_start = False
|
||||
for valid_starting_value in VALID_RESOURCE_PATH_STARTING_VALUES[service]["values"]:
|
||||
if resource_partitions[2].startswith(valid_starting_value):
|
||||
valid_start = True
|
||||
break
|
||||
if not valid_start:
|
||||
self._resource_error = VALID_RESOURCE_PATH_STARTING_VALUES[service]["error_message"].format(
|
||||
values=", ".join(VALID_RESOURCE_PATH_STARTING_VALUES[service]["values"])
|
||||
)
|
||||
|
||||
def _perform_first_legacy_parsing(self):
|
||||
"""This method excludes legacy parsing resources, since that have to be done later."""
|
||||
for statement in self._statements:
|
||||
self._legacy_parse_statement(statement)
|
||||
|
||||
@staticmethod
|
||||
def _legacy_parse_statement(statement):
|
||||
assert statement["Effect"] in VALID_EFFECTS # case-sensitive matching
|
||||
if "Condition" in statement:
|
||||
for condition_key, condition_value in statement["Condition"].items():
|
||||
IAMPolicyDocumentValidator._legacy_parse_condition(condition_key, condition_value)
|
||||
|
||||
@staticmethod
|
||||
def _legacy_parse_resource_like(statement, key):
|
||||
if isinstance(statement[key], string_types):
|
||||
if statement[key] != "*":
|
||||
assert statement[key].count(":") >= 5 or "::" not in statement[key]
|
||||
assert statement[key].split(":")[2] != ""
|
||||
else: # list
|
||||
for resource in statement[key]:
|
||||
if resource != "*":
|
||||
assert resource.count(":") >= 5 or "::" not in resource
|
||||
assert resource[2] != ""
|
||||
|
||||
@staticmethod
|
||||
def _legacy_parse_condition(condition_key, condition_value):
|
||||
stripped_condition_key = IAMPolicyDocumentValidator._strip_condition_key(condition_key)
|
||||
|
||||
if stripped_condition_key.startswith("Date"):
|
||||
for condition_element_key, condition_element_value in condition_value.items():
|
||||
if isinstance(condition_element_value, string_types):
|
||||
IAMPolicyDocumentValidator._legacy_parse_date_condition_value(condition_element_value)
|
||||
else: # it has to be a list
|
||||
for date_condition_value in condition_element_value:
|
||||
IAMPolicyDocumentValidator._legacy_parse_date_condition_value(date_condition_value)
|
||||
|
||||
@staticmethod
|
||||
def _legacy_parse_date_condition_value(date_condition_value):
|
||||
if "t" in date_condition_value.lower() or "-" in date_condition_value:
|
||||
IAMPolicyDocumentValidator._validate_iso_8601_datetime(date_condition_value.lower())
|
||||
else: # timestamp
|
||||
assert 0 <= int(date_condition_value) <= 9223372036854775807
|
||||
|
||||
@staticmethod
|
||||
def _validate_iso_8601_datetime(datetime):
|
||||
datetime_parts = datetime.partition("t")
|
||||
negative_year = datetime_parts[0].startswith("-")
|
||||
date_parts = datetime_parts[0][1:].split("-") if negative_year else datetime_parts[0].split("-")
|
||||
year = "-" + date_parts[0] if negative_year else date_parts[0]
|
||||
assert -292275054 <= int(year) <= 292278993
|
||||
if len(date_parts) > 1:
|
||||
month = date_parts[1]
|
||||
assert 1 <= int(month) <= 12
|
||||
if len(date_parts) > 2:
|
||||
day = date_parts[2]
|
||||
assert 1 <= int(day) <= 31
|
||||
assert len(date_parts) < 4
|
||||
|
||||
time_parts = datetime_parts[2].split(":")
|
||||
if time_parts[0] != "":
|
||||
hours = time_parts[0]
|
||||
assert 0 <= int(hours) <= 23
|
||||
if len(time_parts) > 1:
|
||||
minutes = time_parts[1]
|
||||
assert 0 <= int(minutes) <= 59
|
||||
if len(time_parts) > 2:
|
||||
if "z" in time_parts[2]:
|
||||
seconds_with_decimal_fraction = time_parts[2].partition("z")[0]
|
||||
assert time_parts[2].partition("z")[2] == ""
|
||||
elif "+" in time_parts[2]:
|
||||
seconds_with_decimal_fraction = time_parts[2].partition("+")[0]
|
||||
time_zone_data = time_parts[2].partition("+")[2].partition(":")
|
||||
time_zone_hours = time_zone_data[0]
|
||||
assert len(time_zone_hours) == 2
|
||||
assert 0 <= int(time_zone_hours) <= 23
|
||||
if time_zone_data[1] == ":":
|
||||
time_zone_minutes = time_zone_data[2]
|
||||
assert len(time_zone_minutes) == 2
|
||||
assert 0 <= int(time_zone_minutes) <= 59
|
||||
else:
|
||||
seconds_with_decimal_fraction = time_parts[2]
|
||||
seconds_with_decimal_fraction_partition = seconds_with_decimal_fraction.partition(".")
|
||||
seconds = seconds_with_decimal_fraction_partition[0]
|
||||
assert 0 <= int(seconds) <= 59
|
||||
if seconds_with_decimal_fraction_partition[1] == ".":
|
||||
decimal_seconds = seconds_with_decimal_fraction_partition[2]
|
||||
assert 0 <= int(decimal_seconds) <= 999999999
|
@ -818,12 +818,12 @@ CREATE_POLICY_TEMPLATE = """<CreatePolicyResponse>
|
||||
<Policy>
|
||||
<Arn>{{ policy.arn }}</Arn>
|
||||
<AttachmentCount>{{ policy.attachment_count }}</AttachmentCount>
|
||||
<CreateDate>{{ policy.create_datetime.isoformat() }}</CreateDate>
|
||||
<CreateDate>{{ policy.created_iso_8601 }}</CreateDate>
|
||||
<DefaultVersionId>{{ policy.default_version_id }}</DefaultVersionId>
|
||||
<Path>{{ policy.path }}</Path>
|
||||
<PolicyId>{{ policy.id }}</PolicyId>
|
||||
<PolicyName>{{ policy.name }}</PolicyName>
|
||||
<UpdateDate>{{ policy.update_datetime.isoformat() }}</UpdateDate>
|
||||
<UpdateDate>{{ policy.updated_iso_8601 }}</UpdateDate>
|
||||
</Policy>
|
||||
</CreatePolicyResult>
|
||||
<ResponseMetadata>
|
||||
@ -841,8 +841,8 @@ GET_POLICY_TEMPLATE = """<GetPolicyResponse>
|
||||
<Path>{{ policy.path }}</Path>
|
||||
<Arn>{{ policy.arn }}</Arn>
|
||||
<AttachmentCount>{{ policy.attachment_count }}</AttachmentCount>
|
||||
<CreateDate>{{ policy.create_datetime.isoformat() }}</CreateDate>
|
||||
<UpdateDate>{{ policy.update_datetime.isoformat() }}</UpdateDate>
|
||||
<CreateDate>{{ policy.created_iso_8601 }}</CreateDate>
|
||||
<UpdateDate>{{ policy.updated_iso_8601 }}</UpdateDate>
|
||||
</Policy>
|
||||
</GetPolicyResult>
|
||||
<ResponseMetadata>
|
||||
@ -929,12 +929,12 @@ LIST_POLICIES_TEMPLATE = """<ListPoliciesResponse>
|
||||
<member>
|
||||
<Arn>{{ policy.arn }}</Arn>
|
||||
<AttachmentCount>{{ policy.attachment_count }}</AttachmentCount>
|
||||
<CreateDate>{{ policy.create_datetime.isoformat() }}</CreateDate>
|
||||
<CreateDate>{{ policy.created_iso_8601 }}</CreateDate>
|
||||
<DefaultVersionId>{{ policy.default_version_id }}</DefaultVersionId>
|
||||
<Path>{{ policy.path }}</Path>
|
||||
<PolicyId>{{ policy.id }}</PolicyId>
|
||||
<PolicyName>{{ policy.name }}</PolicyName>
|
||||
<UpdateDate>{{ policy.update_datetime.isoformat() }}</UpdateDate>
|
||||
<UpdateDate>{{ policy.updated_iso_8601 }}</UpdateDate>
|
||||
</member>
|
||||
{% endfor %}
|
||||
</Policies>
|
||||
@ -958,7 +958,7 @@ CREATE_INSTANCE_PROFILE_TEMPLATE = """<CreateInstanceProfileResponse xmlns="http
|
||||
<InstanceProfileName>{{ profile.name }}</InstanceProfileName>
|
||||
<Path>{{ profile.path }}</Path>
|
||||
<Arn>{{ profile.arn }}</Arn>
|
||||
<CreateDate>{{ profile.create_date }}</CreateDate>
|
||||
<CreateDate>{{ profile.created_iso_8601 }}</CreateDate>
|
||||
</InstanceProfile>
|
||||
</CreateInstanceProfileResult>
|
||||
<ResponseMetadata>
|
||||
@ -977,7 +977,7 @@ GET_INSTANCE_PROFILE_TEMPLATE = """<GetInstanceProfileResponse xmlns="https://ia
|
||||
<Arn>{{ role.arn }}</Arn>
|
||||
<RoleName>{{ role.name }}</RoleName>
|
||||
<AssumeRolePolicyDocument>{{ role.assume_role_policy_document }}</AssumeRolePolicyDocument>
|
||||
<CreateDate>{{ role.create_date }}</CreateDate>
|
||||
<CreateDate>{{ role.created_iso_8601 }}</CreateDate>
|
||||
<RoleId>{{ role.id }}</RoleId>
|
||||
</member>
|
||||
{% endfor %}
|
||||
@ -985,7 +985,7 @@ GET_INSTANCE_PROFILE_TEMPLATE = """<GetInstanceProfileResponse xmlns="https://ia
|
||||
<InstanceProfileName>{{ profile.name }}</InstanceProfileName>
|
||||
<Path>{{ profile.path }}</Path>
|
||||
<Arn>{{ profile.arn }}</Arn>
|
||||
<CreateDate>{{ profile.create_date }}</CreateDate>
|
||||
<CreateDate>{{ profile.created_iso_8601 }}</CreateDate>
|
||||
</InstanceProfile>
|
||||
</GetInstanceProfileResult>
|
||||
<ResponseMetadata>
|
||||
@ -1000,7 +1000,7 @@ CREATE_ROLE_TEMPLATE = """<CreateRoleResponse xmlns="https://iam.amazonaws.com/d
|
||||
<Arn>{{ role.arn }}</Arn>
|
||||
<RoleName>{{ role.name }}</RoleName>
|
||||
<AssumeRolePolicyDocument>{{ role.assume_role_policy_document }}</AssumeRolePolicyDocument>
|
||||
<CreateDate>{{ role.create_date }}</CreateDate>
|
||||
<CreateDate>{{ role.created_iso_8601 }}</CreateDate>
|
||||
<RoleId>{{ role.id }}</RoleId>
|
||||
{% if role.permissions_boundary %}
|
||||
<PermissionsBoundary>
|
||||
@ -1041,7 +1041,7 @@ UPDATE_ROLE_DESCRIPTION_TEMPLATE = """<UpdateRoleDescriptionResponse xmlns="http
|
||||
<Arn>{{ role.arn }}</Arn>
|
||||
<RoleName>{{ role.name }}</RoleName>
|
||||
<AssumeRolePolicyDocument>{{ role.assume_role_policy_document }}</AssumeRolePolicyDocument>
|
||||
<CreateDate>{{ role.create_date.isoformat() }}</CreateDate>
|
||||
<CreateDate>{{ role.created_iso_8601 }}</CreateDate>
|
||||
<RoleId>{{ role.id }}</RoleId>
|
||||
{% if role.tags %}
|
||||
<Tags>
|
||||
@ -1067,7 +1067,7 @@ GET_ROLE_TEMPLATE = """<GetRoleResponse xmlns="https://iam.amazonaws.com/doc/201
|
||||
<Arn>{{ role.arn }}</Arn>
|
||||
<RoleName>{{ role.name }}</RoleName>
|
||||
<AssumeRolePolicyDocument>{{ role.assume_role_policy_document }}</AssumeRolePolicyDocument>
|
||||
<CreateDate>{{ role.create_date }}</CreateDate>
|
||||
<CreateDate>{{ role.created_iso_8601 }}</CreateDate>
|
||||
<RoleId>{{ role.id }}</RoleId>
|
||||
{% if role.tags %}
|
||||
<Tags>
|
||||
@ -1108,7 +1108,7 @@ LIST_ROLES_TEMPLATE = """<ListRolesResponse xmlns="https://iam.amazonaws.com/doc
|
||||
<Arn>{{ role.arn }}</Arn>
|
||||
<RoleName>{{ role.name }}</RoleName>
|
||||
<AssumeRolePolicyDocument>{{ role.assume_role_policy_document }}</AssumeRolePolicyDocument>
|
||||
<CreateDate>{{ role.create_date }}</CreateDate>
|
||||
<CreateDate>{{ role.created_iso_8601 }}</CreateDate>
|
||||
<RoleId>{{ role.id }}</RoleId>
|
||||
{% if role.permissions_boundary %}
|
||||
<PermissionsBoundary>
|
||||
@ -1144,8 +1144,8 @@ CREATE_POLICY_VERSION_TEMPLATE = """<CreatePolicyVersionResponse xmlns="https://
|
||||
<PolicyVersion>
|
||||
<Document>{{ policy_version.document }}</Document>
|
||||
<VersionId>{{ policy_version.version_id }}</VersionId>
|
||||
<IsDefaultVersion>{{ policy_version.is_default }}</IsDefaultVersion>
|
||||
<CreateDate>{{ policy_version.create_datetime }}</CreateDate>
|
||||
<IsDefaultVersion>{{ policy_version.is_default | lower }}</IsDefaultVersion>
|
||||
<CreateDate>{{ policy_version.created_iso_8601 }}</CreateDate>
|
||||
</PolicyVersion>
|
||||
</CreatePolicyVersionResult>
|
||||
<ResponseMetadata>
|
||||
@ -1158,8 +1158,8 @@ GET_POLICY_VERSION_TEMPLATE = """<GetPolicyVersionResponse xmlns="https://iam.am
|
||||
<PolicyVersion>
|
||||
<Document>{{ policy_version.document }}</Document>
|
||||
<VersionId>{{ policy_version.version_id }}</VersionId>
|
||||
<IsDefaultVersion>{{ policy_version.is_default }}</IsDefaultVersion>
|
||||
<CreateDate>{{ policy_version.create_datetime }}</CreateDate>
|
||||
<IsDefaultVersion>{{ policy_version.is_default | lower }}</IsDefaultVersion>
|
||||
<CreateDate>{{ policy_version.created_iso_8601 }}</CreateDate>
|
||||
</PolicyVersion>
|
||||
</GetPolicyVersionResult>
|
||||
<ResponseMetadata>
|
||||
@ -1175,8 +1175,8 @@ LIST_POLICY_VERSIONS_TEMPLATE = """<ListPolicyVersionsResponse xmlns="https://ia
|
||||
<member>
|
||||
<Document>{{ policy_version.document }}</Document>
|
||||
<VersionId>{{ policy_version.version_id }}</VersionId>
|
||||
<IsDefaultVersion>{{ policy_version.is_default }}</IsDefaultVersion>
|
||||
<CreateDate>{{ policy_version.create_datetime }}</CreateDate>
|
||||
<IsDefaultVersion>{{ policy_version.is_default | lower }}</IsDefaultVersion>
|
||||
<CreateDate>{{ policy_version.created_iso_8601 }}</CreateDate>
|
||||
</member>
|
||||
{% endfor %}
|
||||
</Versions>
|
||||
@ -1200,7 +1200,7 @@ LIST_INSTANCE_PROFILES_TEMPLATE = """<ListInstanceProfilesResponse xmlns="https:
|
||||
<Arn>{{ role.arn }}</Arn>
|
||||
<RoleName>{{ role.name }}</RoleName>
|
||||
<AssumeRolePolicyDocument>{{ role.assume_role_policy_document }}</AssumeRolePolicyDocument>
|
||||
<CreateDate>{{ role.create_date }}</CreateDate>
|
||||
<CreateDate>{{ role.created_iso_8601 }}</CreateDate>
|
||||
<RoleId>{{ role.id }}</RoleId>
|
||||
</member>
|
||||
{% endfor %}
|
||||
@ -1208,7 +1208,7 @@ LIST_INSTANCE_PROFILES_TEMPLATE = """<ListInstanceProfilesResponse xmlns="https:
|
||||
<InstanceProfileName>{{ instance.name }}</InstanceProfileName>
|
||||
<Path>{{ instance.path }}</Path>
|
||||
<Arn>{{ instance.arn }}</Arn>
|
||||
<CreateDate>{{ instance.create_date }}</CreateDate>
|
||||
<CreateDate>{{ instance.created_iso_8601 }}</CreateDate>
|
||||
</member>
|
||||
{% endfor %}
|
||||
</InstanceProfiles>
|
||||
@ -1287,7 +1287,7 @@ CREATE_GROUP_TEMPLATE = """<CreateGroupResponse>
|
||||
<GroupName>{{ group.name }}</GroupName>
|
||||
<GroupId>{{ group.id }}</GroupId>
|
||||
<Arn>{{ group.arn }}</Arn>
|
||||
<CreateDate>{{ group.create_date }}</CreateDate>
|
||||
<CreateDate>{{ group.created_iso_8601 }}</CreateDate>
|
||||
</Group>
|
||||
</CreateGroupResult>
|
||||
<ResponseMetadata>
|
||||
@ -1302,7 +1302,7 @@ GET_GROUP_TEMPLATE = """<GetGroupResponse>
|
||||
<GroupName>{{ group.name }}</GroupName>
|
||||
<GroupId>{{ group.id }}</GroupId>
|
||||
<Arn>{{ group.arn }}</Arn>
|
||||
<CreateDate>{{ group.create_date }}</CreateDate>
|
||||
<CreateDate>{{ group.created_iso_8601 }}</CreateDate>
|
||||
</Group>
|
||||
<Users>
|
||||
{% for user in group.users %}
|
||||
@ -1349,6 +1349,7 @@ LIST_GROUPS_FOR_USER_TEMPLATE = """<ListGroupsForUserResponse>
|
||||
<GroupName>{{ group.name }}</GroupName>
|
||||
<GroupId>{{ group.id }}</GroupId>
|
||||
<Arn>{{ group.arn }}</Arn>
|
||||
<CreateDate>{{ group.created_iso_8601 }}</CreateDate>
|
||||
</member>
|
||||
{% endfor %}
|
||||
</Groups>
|
||||
@ -1493,6 +1494,7 @@ CREATE_ACCESS_KEY_TEMPLATE = """<CreateAccessKeyResponse>
|
||||
<AccessKeyId>{{ key.access_key_id }}</AccessKeyId>
|
||||
<Status>{{ key.status }}</Status>
|
||||
<SecretAccessKey>{{ key.secret_access_key }}</SecretAccessKey>
|
||||
<CreateDate>{{ key.created_iso_8601 }}</CreateDate>
|
||||
</AccessKey>
|
||||
</CreateAccessKeyResult>
|
||||
<ResponseMetadata>
|
||||
@ -1509,7 +1511,7 @@ LIST_ACCESS_KEYS_TEMPLATE = """<ListAccessKeysResponse>
|
||||
<UserName>{{ user_name }}</UserName>
|
||||
<AccessKeyId>{{ key.access_key_id }}</AccessKeyId>
|
||||
<Status>{{ key.status }}</Status>
|
||||
<CreateDate>{{ key.create_date }}</CreateDate>
|
||||
<CreateDate>{{ key.created_iso_8601 }}</CreateDate>
|
||||
</member>
|
||||
{% endfor %}
|
||||
</AccessKeyMetadata>
|
||||
@ -1577,7 +1579,7 @@ LIST_INSTANCE_PROFILES_FOR_ROLE_TEMPLATE = """<ListInstanceProfilesForRoleRespon
|
||||
<Arn>{{ role.arn }}</Arn>
|
||||
<RoleName>{{ role.name }}</RoleName>
|
||||
<AssumeRolePolicyDocument>{{ role.assume_policy_document }}</AssumeRolePolicyDocument>
|
||||
<CreateDate>{{ role.create_date }}</CreateDate>
|
||||
<CreateDate>{{ role.created_iso_8601 }}</CreateDate>
|
||||
<RoleId>{{ role.id }}</RoleId>
|
||||
</member>
|
||||
{% endfor %}
|
||||
@ -1585,7 +1587,7 @@ LIST_INSTANCE_PROFILES_FOR_ROLE_TEMPLATE = """<ListInstanceProfilesForRoleRespon
|
||||
<InstanceProfileName>{{ profile.name }}</InstanceProfileName>
|
||||
<Path>{{ profile.path }}</Path>
|
||||
<Arn>{{ profile.arn }}</Arn>
|
||||
<CreateDate>{{ profile.create_date }}</CreateDate>
|
||||
<CreateDate>{{ profile.created_iso_8601 }}</CreateDate>
|
||||
</member>
|
||||
{% endfor %}
|
||||
</InstanceProfiles>
|
||||
@ -1651,6 +1653,7 @@ LIST_GROUPS_FOR_USER_TEMPLATE = """<ListGroupsForUserResponse>
|
||||
<GroupName>{{ group.name }}</GroupName>
|
||||
<GroupId>{{ group.id }}</GroupId>
|
||||
<Arn>{{ group.arn }}</Arn>
|
||||
<CreateDate>{{ group.created_iso_8601 }}</CreateDate>
|
||||
</member>
|
||||
{% endfor %}
|
||||
</Groups>
|
||||
@ -1704,7 +1707,7 @@ GET_ACCOUNT_AUTHORIZATION_DETAILS_TEMPLATE = """<GetAccountAuthorizationDetailsR
|
||||
<GroupName>{{ group.name }}</GroupName>
|
||||
<Path>{{ group.path }}</Path>
|
||||
<Arn>{{ group.arn }}</Arn>
|
||||
<CreateDate>{{ group.create_date }}</CreateDate>
|
||||
<CreateDate>{{ group.created_iso_8601 }}</CreateDate>
|
||||
<GroupPolicyList>
|
||||
{% for policy in group.policies %}
|
||||
<member>
|
||||
@ -1754,7 +1757,7 @@ GET_ACCOUNT_AUTHORIZATION_DETAILS_TEMPLATE = """<GetAccountAuthorizationDetailsR
|
||||
<Arn>{{ role.arn }}</Arn>
|
||||
<RoleName>{{ role.name }}</RoleName>
|
||||
<AssumeRolePolicyDocument>{{ role.assume_role_policy_document }}</AssumeRolePolicyDocument>
|
||||
<CreateDate>{{ role.create_date }}</CreateDate>
|
||||
<CreateDate>{{ role.created_iso_8601 }}</CreateDate>
|
||||
<RoleId>{{ role.id }}</RoleId>
|
||||
</member>
|
||||
{% endfor %}
|
||||
@ -1762,7 +1765,7 @@ GET_ACCOUNT_AUTHORIZATION_DETAILS_TEMPLATE = """<GetAccountAuthorizationDetailsR
|
||||
<InstanceProfileName>{{ profile.name }}</InstanceProfileName>
|
||||
<Path>{{ profile.path }}</Path>
|
||||
<Arn>{{ profile.arn }}</Arn>
|
||||
<CreateDate>{{ profile.create_date }}</CreateDate>
|
||||
<CreateDate>{{ profile.created_iso_8601 }}</CreateDate>
|
||||
</member>
|
||||
{% endfor %}
|
||||
</InstanceProfileList>
|
||||
@ -1770,7 +1773,7 @@ GET_ACCOUNT_AUTHORIZATION_DETAILS_TEMPLATE = """<GetAccountAuthorizationDetailsR
|
||||
<Arn>{{ role.arn }}</Arn>
|
||||
<RoleName>{{ role.name }}</RoleName>
|
||||
<AssumeRolePolicyDocument>{{ role.assume_role_policy_document }}</AssumeRolePolicyDocument>
|
||||
<CreateDate>{{ role.create_date }}</CreateDate>
|
||||
<CreateDate>{{ role.created_iso_8601 }}</CreateDate>
|
||||
<RoleId>{{ role.id }}</RoleId>
|
||||
</member>
|
||||
{% endfor %}
|
||||
@ -1786,17 +1789,17 @@ GET_ACCOUNT_AUTHORIZATION_DETAILS_TEMPLATE = """<GetAccountAuthorizationDetailsR
|
||||
{% for policy_version in policy.versions %}
|
||||
<member>
|
||||
<Document>{{ policy_version.document }}</Document>
|
||||
<IsDefaultVersion>{{ policy_version.is_default }}</IsDefaultVersion>
|
||||
<IsDefaultVersion>{{ policy_version.is_default | lower }}</IsDefaultVersion>
|
||||
<VersionId>{{ policy_version.version_id }}</VersionId>
|
||||
<CreateDate>{{ policy_version.create_datetime }}</CreateDate>
|
||||
<CreateDate>{{ policy_version.created_iso_8601 }}</CreateDate>
|
||||
</member>
|
||||
{% endfor %}
|
||||
</PolicyVersionList>
|
||||
<Arn>{{ policy.arn }}</Arn>
|
||||
<AttachmentCount>1</AttachmentCount>
|
||||
<CreateDate>{{ policy.create_datetime }}</CreateDate>
|
||||
<CreateDate>{{ policy.created_iso_8601 }}</CreateDate>
|
||||
<IsAttachable>true</IsAttachable>
|
||||
<UpdateDate>{{ policy.update_datetime }}</UpdateDate>
|
||||
<UpdateDate>{{ policy.updated_iso_8601 }}</UpdateDate>
|
||||
</member>
|
||||
{% endfor %}
|
||||
</Policies>
|
||||
|
@ -7,7 +7,7 @@ import six
|
||||
def random_alphanumeric(length):
|
||||
return ''.join(six.text_type(
|
||||
random.choice(
|
||||
string.ascii_letters + string.digits
|
||||
string.ascii_letters + string.digits + "+" + "/"
|
||||
)) for _ in range(length)
|
||||
)
|
||||
|
||||
|
@ -123,17 +123,12 @@ class Stream(BaseModel):
|
||||
self.tags = {}
|
||||
self.status = "ACTIVE"
|
||||
|
||||
if six.PY3:
|
||||
izip_longest = itertools.zip_longest
|
||||
else:
|
||||
izip_longest = itertools.izip_longest
|
||||
step = 2**128 // shard_count
|
||||
hash_ranges = itertools.chain(map(lambda i: (i, i * step, (i + 1) * step),
|
||||
range(shard_count - 1)),
|
||||
[(shard_count - 1, (shard_count - 1) * step, 2**128)])
|
||||
for index, start, end in hash_ranges:
|
||||
|
||||
for index, start, end in izip_longest(range(shard_count),
|
||||
range(0, 2**128, 2 **
|
||||
128 // shard_count),
|
||||
range(2**128 // shard_count, 2 **
|
||||
128, 2**128 // shard_count),
|
||||
fillvalue=2**128):
|
||||
shard = Shard(index, start, end)
|
||||
self.shards[shard.shard_id] = shard
|
||||
|
||||
|
@ -268,10 +268,26 @@ class fakesock(object):
|
||||
_sent_data = []
|
||||
|
||||
def __init__(self, family=socket.AF_INET, type=socket.SOCK_STREAM,
|
||||
protocol=0):
|
||||
self.truesock = (old_socket(family, type, protocol)
|
||||
if httpretty.allow_net_connect
|
||||
else None)
|
||||
proto=0, fileno=None, _sock=None):
|
||||
"""
|
||||
Matches both the Python 2 API:
|
||||
def __init__(self, family=AF_INET, type=SOCK_STREAM, proto=0, _sock=None):
|
||||
https://github.com/python/cpython/blob/2.7/Lib/socket.py
|
||||
|
||||
and the Python 3 API:
|
||||
def __init__(self, family=-1, type=-1, proto=-1, fileno=None):
|
||||
https://github.com/python/cpython/blob/3.5/Lib/socket.py
|
||||
"""
|
||||
if httpretty.allow_net_connect:
|
||||
if PY3:
|
||||
self.truesock = old_socket(family, type, proto, fileno)
|
||||
else:
|
||||
# If Python 2, if parameters are passed as arguments, instead of kwargs,
|
||||
# the 4th argument `_sock` will be interpreted as the `fileno`.
|
||||
# Check if _sock is none, and if so, pass fileno.
|
||||
self.truesock = old_socket(family, type, proto, fileno or _sock)
|
||||
else:
|
||||
self.truesock = None
|
||||
self._closed = True
|
||||
self.fd = FakeSockFile()
|
||||
self.fd.socket = self
|
||||
|
@ -120,7 +120,7 @@ class RecordSet(BaseModel):
|
||||
properties["HostedZoneId"])
|
||||
|
||||
try:
|
||||
hosted_zone.delete_rrset_by_name(resource_name)
|
||||
hosted_zone.delete_rrset({'Name': resource_name})
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
@ -171,7 +171,13 @@ class RecordSet(BaseModel):
|
||||
self.hosted_zone_name)
|
||||
if not hosted_zone:
|
||||
hosted_zone = route53_backend.get_hosted_zone(self.hosted_zone_id)
|
||||
hosted_zone.delete_rrset_by_name(self.name)
|
||||
hosted_zone.delete_rrset({'Name': self.name, 'Type': self.type_})
|
||||
|
||||
|
||||
def reverse_domain_name(domain_name):
|
||||
if domain_name.endswith('.'): # normalize without trailing dot
|
||||
domain_name = domain_name[:-1]
|
||||
return '.'.join(reversed(domain_name.split('.')))
|
||||
|
||||
|
||||
class FakeZone(BaseModel):
|
||||
@ -199,9 +205,13 @@ class FakeZone(BaseModel):
|
||||
self.rrsets.append(new_rrset)
|
||||
return new_rrset
|
||||
|
||||
def delete_rrset_by_name(self, name):
|
||||
def delete_rrset(self, rrset):
|
||||
self.rrsets = [
|
||||
record_set for record_set in self.rrsets if record_set.name != name]
|
||||
record_set
|
||||
for record_set in self.rrsets
|
||||
if record_set.name != rrset['Name'] or
|
||||
(rrset.get('Type') is not None and record_set.type_ != rrset['Type'])
|
||||
]
|
||||
|
||||
def delete_rrset_by_id(self, set_identifier):
|
||||
self.rrsets = [
|
||||
@ -209,12 +219,15 @@ class FakeZone(BaseModel):
|
||||
|
||||
def get_record_sets(self, start_type, start_name):
|
||||
record_sets = list(self.rrsets) # Copy the list
|
||||
if start_name:
|
||||
record_sets = [
|
||||
record_set
|
||||
for record_set in record_sets
|
||||
if reverse_domain_name(record_set.name) >= reverse_domain_name(start_name)
|
||||
]
|
||||
if start_type:
|
||||
record_sets = [
|
||||
record_set for record_set in record_sets if record_set.type_ >= start_type]
|
||||
if start_name:
|
||||
record_sets = [
|
||||
record_set for record_set in record_sets if record_set.name >= start_name]
|
||||
|
||||
return record_sets
|
||||
|
||||
|
@ -144,7 +144,7 @@ class Route53(BaseResponse):
|
||||
the_zone.delete_rrset_by_id(
|
||||
record_set["SetIdentifier"])
|
||||
else:
|
||||
the_zone.delete_rrset_by_name(record_set["Name"])
|
||||
the_zone.delete_rrset(record_set)
|
||||
|
||||
return 200, headers, CHANGE_RRSET_RESPONSE
|
||||
|
||||
|
@ -12,7 +12,7 @@ from boto3 import Session
|
||||
|
||||
from moto.compat import OrderedDict
|
||||
from moto.core import BaseBackend, BaseModel
|
||||
from moto.core.utils import iso_8601_datetime_with_milliseconds
|
||||
from moto.core.utils import iso_8601_datetime_with_milliseconds, camelcase_to_underscores
|
||||
from moto.sqs import sqs_backends
|
||||
from moto.awslambda import lambda_backends
|
||||
|
||||
@ -243,11 +243,14 @@ class SNSBackend(BaseBackend):
|
||||
def update_sms_attributes(self, attrs):
|
||||
self.sms_attributes.update(attrs)
|
||||
|
||||
def create_topic(self, name):
|
||||
def create_topic(self, name, attributes=None):
|
||||
fails_constraints = not re.match(r'^[a-zA-Z0-9_-]{1,256}$', name)
|
||||
if fails_constraints:
|
||||
raise InvalidParameterValue("Topic names must be made up of only uppercase and lowercase ASCII letters, numbers, underscores, and hyphens, and must be between 1 and 256 characters long.")
|
||||
candidate_topic = Topic(name, self)
|
||||
if attributes:
|
||||
for attribute in attributes:
|
||||
setattr(candidate_topic, camelcase_to_underscores(attribute), attributes[attribute])
|
||||
if candidate_topic.arn in self.topics:
|
||||
return self.topics[candidate_topic.arn]
|
||||
else:
|
||||
|
@ -75,7 +75,8 @@ class SNSResponse(BaseResponse):
|
||||
|
||||
def create_topic(self):
|
||||
name = self._get_param('Name')
|
||||
topic = self.backend.create_topic(name)
|
||||
attributes = self._get_attributes()
|
||||
topic = self.backend.create_topic(name, attributes)
|
||||
|
||||
if self.request_json:
|
||||
return json.dumps({
|
||||
|
25
other_langs/sqsSample.scala
Normal file
25
other_langs/sqsSample.scala
Normal file
@ -0,0 +1,25 @@
|
||||
package com.amazonaws.examples
|
||||
|
||||
import com.amazonaws.client.builder.AwsClientBuilder
|
||||
import com.amazonaws.regions.{Region, Regions}
|
||||
import com.amazonaws.services.sqs.AmazonSQSClientBuilder
|
||||
|
||||
import scala.jdk.CollectionConverters._
|
||||
|
||||
object QueueTest extends App {
|
||||
val region = Region.getRegion(Regions.US_WEST_2).getName
|
||||
val serviceEndpoint = "http://localhost:5000"
|
||||
|
||||
val amazonSqs = AmazonSQSClientBuilder.standard()
|
||||
.withEndpointConfiguration(
|
||||
new AwsClientBuilder.EndpointConfiguration(serviceEndpoint, region))
|
||||
.build
|
||||
|
||||
val queueName = "my-first-queue"
|
||||
amazonSqs.createQueue(queueName)
|
||||
|
||||
val urls = amazonSqs.listQueues().getQueueUrls.asScala
|
||||
println("Listing queues")
|
||||
println(urls.map(url => s" - $url").mkString(System.lineSeparator))
|
||||
println()
|
||||
}
|
@ -48,7 +48,8 @@ for policy_name in policies:
|
||||
PolicyArn=policies[policy_name]['Arn'],
|
||||
VersionId=policies[policy_name]['DefaultVersionId'])
|
||||
for key in response['PolicyVersion']:
|
||||
policies[policy_name][key] = response['PolicyVersion'][key]
|
||||
if key != "CreateDate": # the policy's CreateDate should not be overwritten by its version's CreateDate
|
||||
policies[policy_name][key] = response['PolicyVersion'][key]
|
||||
|
||||
with open(output_file, 'w') as f:
|
||||
triple_quote = '\"\"\"'
|
||||
|
16
setup.py
16
setup.py
@ -18,17 +18,27 @@ def read(*parts):
|
||||
return fp.read()
|
||||
|
||||
|
||||
def get_version():
|
||||
version_file = read('moto', '__init__.py')
|
||||
version_match = re.search(r'^__version__ = [\'"]([^\'"]*)[\'"]',
|
||||
version_file, re.MULTILINE)
|
||||
if version_match:
|
||||
return version_match.group(1)
|
||||
raise RuntimeError('Unable to find version string.')
|
||||
|
||||
|
||||
install_requires = [
|
||||
"Jinja2>=2.10.1",
|
||||
"boto>=2.36.0",
|
||||
"boto3>=1.9.86",
|
||||
"botocore>=1.12.86",
|
||||
"cryptography>=2.3.0",
|
||||
"datetime",
|
||||
"requests>=2.5",
|
||||
"xmltodict",
|
||||
"six>1.9",
|
||||
"werkzeug",
|
||||
"PyYAML==3.13",
|
||||
"PyYAML>=5.1",
|
||||
"pytz",
|
||||
"python-dateutil<3.0.0,>=2.1",
|
||||
"python-jose<4.0.0",
|
||||
@ -56,7 +66,7 @@ else:
|
||||
|
||||
setup(
|
||||
name='moto',
|
||||
version='1.3.8',
|
||||
version=get_version(),
|
||||
description='A library that allows your python tests to easily'
|
||||
' mock out the boto library',
|
||||
long_description=read('README.md'),
|
||||
@ -79,10 +89,10 @@ setup(
|
||||
"Programming Language :: Python :: 2",
|
||||
"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",
|
||||
"Programming Language :: Python :: 3.7",
|
||||
"License :: OSI Approved :: Apache Software License",
|
||||
"Topic :: Software Development :: Testing",
|
||||
],
|
||||
|
48
tests/test_core/test_socket.py
Normal file
48
tests/test_core/test_socket.py
Normal file
@ -0,0 +1,48 @@
|
||||
import unittest
|
||||
from moto import mock_dynamodb2_deprecated, mock_dynamodb2
|
||||
import socket
|
||||
|
||||
from six import PY3
|
||||
|
||||
|
||||
class TestSocketPair(unittest.TestCase):
|
||||
|
||||
@mock_dynamodb2_deprecated
|
||||
def test_asyncio_deprecated(self):
|
||||
if PY3:
|
||||
self.assertIn(
|
||||
'moto.packages.httpretty.core.fakesock.socket',
|
||||
str(socket.socket),
|
||||
'Our mock should be present'
|
||||
)
|
||||
import asyncio
|
||||
self.assertIsNotNone(asyncio.get_event_loop())
|
||||
|
||||
@mock_dynamodb2_deprecated
|
||||
def test_socket_pair_deprecated(self):
|
||||
|
||||
# In Python2, the fakesocket is not set, for some reason.
|
||||
if PY3:
|
||||
self.assertIn(
|
||||
'moto.packages.httpretty.core.fakesock.socket',
|
||||
str(socket.socket),
|
||||
'Our mock should be present'
|
||||
)
|
||||
a, b = socket.socketpair()
|
||||
self.assertIsNotNone(a)
|
||||
self.assertIsNotNone(b)
|
||||
if a:
|
||||
a.close()
|
||||
if b:
|
||||
b.close()
|
||||
|
||||
|
||||
@mock_dynamodb2
|
||||
def test_socket_pair(self):
|
||||
a, b = socket.socketpair()
|
||||
self.assertIsNotNone(a)
|
||||
self.assertIsNotNone(b)
|
||||
if a:
|
||||
a.close()
|
||||
if b:
|
||||
b.close()
|
@ -452,6 +452,90 @@ def test_basic_projection_expressions():
|
||||
assert 'body' in results['Items'][1]
|
||||
assert 'forum_name' in results['Items'][1]
|
||||
|
||||
@mock_dynamodb2
|
||||
def test_basic_projection_expressions_using_scan():
|
||||
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 scan returning all items
|
||||
results = table.scan(
|
||||
FilterExpression=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.scan(
|
||||
FilterExpression=Key('forum_name').eq(
|
||||
'the-key'),
|
||||
ProjectionExpression='body'
|
||||
)
|
||||
|
||||
assert 'body' in results['Items'][0]
|
||||
assert 'subject' not in results['Items'][0]
|
||||
assert 'forum_name' not in results['Items'][0]
|
||||
assert 'body' in results['Items'][1]
|
||||
assert 'subject' not in results['Items'][1]
|
||||
assert 'forum_name' not in results['Items'][1]
|
||||
|
||||
# The projection expression should not remove data from storage
|
||||
results = table.query(
|
||||
KeyConditionExpression=Key('forum_name').eq(
|
||||
'the-key'),
|
||||
)
|
||||
assert 'subject' in results['Items'][0]
|
||||
assert 'body' in results['Items'][1]
|
||||
assert 'forum_name' in results['Items'][1]
|
||||
|
||||
|
||||
@mock_dynamodb2
|
||||
def test_basic_projection_expressions_with_attr_expression_names():
|
||||
@ -519,6 +603,84 @@ def test_basic_projection_expressions_with_attr_expression_names():
|
||||
assert 'attachment' in results['Items'][0]
|
||||
assert results['Items'][0]['attachment'] == 'something'
|
||||
|
||||
@mock_dynamodb2
|
||||
def test_basic_projection_expressions_using_scan_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 scan returning all items
|
||||
|
||||
results = table.scan(
|
||||
FilterExpression=Key('forum_name').eq(
|
||||
'the-key'),
|
||||
ProjectionExpression='#rl, #rt, subject',
|
||||
ExpressionAttributeNames={
|
||||
'#rl': 'body',
|
||||
'#rt': 'attachment'
|
||||
},
|
||||
)
|
||||
|
||||
assert 'body' in results['Items'][0]
|
||||
assert 'attachment' in results['Items'][0]
|
||||
assert 'subject' in results['Items'][0]
|
||||
assert 'form_name' not in results['Items'][0]
|
||||
|
||||
# Test without a FilterExpression
|
||||
results = table.scan(
|
||||
ProjectionExpression='#rl, #rt, subject',
|
||||
ExpressionAttributeNames={
|
||||
'#rl': 'body',
|
||||
'#rt': 'attachment'
|
||||
},
|
||||
)
|
||||
|
||||
assert 'body' in results['Items'][0]
|
||||
assert 'attachment' in results['Items'][0]
|
||||
assert 'subject' in results['Items'][0]
|
||||
assert 'form_name' not in results['Items'][0]
|
||||
|
||||
|
||||
@mock_dynamodb2
|
||||
def test_put_item_returns_consumed_capacity():
|
||||
|
@ -1,5 +1,7 @@
|
||||
from __future__ import unicode_literals
|
||||
# Ensure 'assert_raises' context manager support for Python 2.6
|
||||
from botocore.exceptions import ClientError
|
||||
|
||||
import tests.backport_assert_raises
|
||||
from nose.tools import assert_raises
|
||||
|
||||
@ -679,8 +681,8 @@ def test_modify_instance_attribute_security_groups():
|
||||
reservation = conn.run_instances('ami-1234abcd')
|
||||
instance = reservation.instances[0]
|
||||
|
||||
sg_id = 'sg-1234abcd'
|
||||
sg_id2 = 'sg-abcd4321'
|
||||
sg_id = conn.create_security_group('test security group', 'this is a test security group').id
|
||||
sg_id2 = conn.create_security_group('test security group 2', 'this is a test security group 2').id
|
||||
|
||||
with assert_raises(EC2ResponseError) as ex:
|
||||
instance.modify_attribute("groupSet", [sg_id, sg_id2], dry_run=True)
|
||||
@ -1255,6 +1257,7 @@ def test_create_instance_ebs_optimized():
|
||||
instance.load()
|
||||
instance.ebs_optimized.should.be(False)
|
||||
|
||||
|
||||
@mock_ec2
|
||||
def test_run_multiple_instances_in_same_command():
|
||||
instance_count = 4
|
||||
@ -1269,3 +1272,37 @@ def test_run_multiple_instances_in_same_command():
|
||||
instances = reservations[0]['Instances']
|
||||
for i in range(0, instance_count):
|
||||
instances[i]['AmiLaunchIndex'].should.be(i)
|
||||
|
||||
|
||||
@mock_ec2
|
||||
def test_describe_instance_attribute():
|
||||
client = boto3.client('ec2', region_name='us-east-1')
|
||||
security_group_id = client.create_security_group(
|
||||
GroupName='test security group', Description='this is a test security group')['GroupId']
|
||||
client.run_instances(ImageId='ami-1234abcd',
|
||||
MinCount=1,
|
||||
MaxCount=1,
|
||||
SecurityGroupIds=[security_group_id])
|
||||
instance_id = client.describe_instances()['Reservations'][0]['Instances'][0]['InstanceId']
|
||||
|
||||
valid_instance_attributes = ['instanceType', 'kernel', 'ramdisk', 'userData', 'disableApiTermination', 'instanceInitiatedShutdownBehavior', 'rootDeviceName', 'blockDeviceMapping', 'productCodes', 'sourceDestCheck', 'groupSet', 'ebsOptimized', 'sriovNetSupport']
|
||||
|
||||
for valid_instance_attribute in valid_instance_attributes:
|
||||
response = client.describe_instance_attribute(InstanceId=instance_id, Attribute=valid_instance_attribute)
|
||||
if valid_instance_attribute == "groupSet":
|
||||
response.should.have.key("Groups")
|
||||
response["Groups"].should.have.length_of(1)
|
||||
response["Groups"][0]["GroupId"].should.equal(security_group_id)
|
||||
elif valid_instance_attribute == "userData":
|
||||
response.should.have.key("UserData")
|
||||
response["UserData"].should.be.empty
|
||||
|
||||
invalid_instance_attributes = ['abc', 'Kernel', 'RamDisk', 'userdata', 'iNsTaNcEtYpE']
|
||||
|
||||
for invalid_instance_attribute in invalid_instance_attributes:
|
||||
with assert_raises(ClientError) as ex:
|
||||
client.describe_instance_attribute(InstanceId=instance_id, Attribute=invalid_instance_attribute)
|
||||
ex.exception.response['Error']['Code'].should.equal('InvalidParameterValue')
|
||||
ex.exception.response['ResponseMetadata']['HTTPStatusCode'].should.equal(400)
|
||||
message = 'Value ({invalid_instance_attribute}) for parameter attribute is invalid. Unknown attribute.'.format(invalid_instance_attribute=invalid_instance_attribute)
|
||||
ex.exception.response['Error']['Message'].should.equal(message)
|
||||
|
@ -3,6 +3,8 @@ from __future__ import unicode_literals
|
||||
import hashlib
|
||||
import json
|
||||
from datetime import datetime
|
||||
from freezegun import freeze_time
|
||||
import os
|
||||
from random import random
|
||||
|
||||
import re
|
||||
@ -13,6 +15,7 @@ from botocore.exceptions import ClientError, ParamValidationError
|
||||
from dateutil.tz import tzlocal
|
||||
|
||||
from moto import mock_ecr
|
||||
from nose import SkipTest
|
||||
|
||||
|
||||
def _create_image_digest(contents=None):
|
||||
@ -198,6 +201,42 @@ def test_put_image():
|
||||
response['image']['repositoryName'].should.equal('test_repository')
|
||||
response['image']['registryId'].should.equal('012345678910')
|
||||
|
||||
|
||||
@mock_ecr
|
||||
def test_put_image_with_push_date():
|
||||
if os.environ.get('TEST_SERVER_MODE', 'false').lower() == 'true':
|
||||
raise SkipTest('Cant manipulate time in server mode')
|
||||
|
||||
client = boto3.client('ecr', region_name='us-east-1')
|
||||
_ = client.create_repository(
|
||||
repositoryName='test_repository'
|
||||
)
|
||||
|
||||
with freeze_time('2018-08-28 00:00:00'):
|
||||
image1_date = datetime.now()
|
||||
_ = client.put_image(
|
||||
repositoryName='test_repository',
|
||||
imageManifest=json.dumps(_create_image_manifest()),
|
||||
imageTag='latest'
|
||||
)
|
||||
|
||||
with freeze_time('2019-05-31 00:00:00'):
|
||||
image2_date = datetime.now()
|
||||
_ = client.put_image(
|
||||
repositoryName='test_repository',
|
||||
imageManifest=json.dumps(_create_image_manifest()),
|
||||
imageTag='latest'
|
||||
)
|
||||
|
||||
describe_response = client.describe_images(repositoryName='test_repository')
|
||||
|
||||
type(describe_response['imageDetails']).should.be(list)
|
||||
len(describe_response['imageDetails']).should.be(2)
|
||||
|
||||
set([describe_response['imageDetails'][0]['imagePushedAt'],
|
||||
describe_response['imageDetails'][1]['imagePushedAt']]).should.equal(set([image1_date, image2_date]))
|
||||
|
||||
|
||||
@mock_ecr
|
||||
def test_put_image_with_multiple_tags():
|
||||
client = boto3.client('ecr', region_name='us-east-1')
|
||||
@ -240,6 +279,7 @@ def test_put_image_with_multiple_tags():
|
||||
len(response2['imageDetails'][0]['imageTags']).should.be(2)
|
||||
response2['imageDetails'][0]['imageTags'].should.be.equal(['v1', 'latest'])
|
||||
|
||||
|
||||
@mock_ecr
|
||||
def test_list_images():
|
||||
client = boto3.client('ecr', region_name='us-east-1')
|
||||
@ -700,7 +740,7 @@ def test_batch_get_image_no_tags():
|
||||
@mock_ecr
|
||||
def test_batch_delete_image_by_tag():
|
||||
client = boto3.client('ecr', region_name='us-east-1')
|
||||
_ = client.create_repository(
|
||||
client.create_repository(
|
||||
repositoryName='test_repository'
|
||||
)
|
||||
|
||||
@ -708,14 +748,13 @@ def test_batch_delete_image_by_tag():
|
||||
|
||||
tags = ['v1', 'v1.0', 'latest']
|
||||
for tag in tags:
|
||||
put_response = client.put_image(
|
||||
client.put_image(
|
||||
repositoryName='test_repository',
|
||||
imageManifest=json.dumps(manifest),
|
||||
imageTag=tag,
|
||||
)
|
||||
|
||||
describe_response1 = client.describe_images(repositoryName='test_repository')
|
||||
image_digest = describe_response1['imageDetails'][0]['imageDigest']
|
||||
|
||||
batch_delete_response = client.batch_delete_image(
|
||||
registryId='012345678910',
|
||||
@ -744,10 +783,52 @@ def test_batch_delete_image_by_tag():
|
||||
len(batch_delete_response['failures']).should.be(0)
|
||||
|
||||
|
||||
@mock_ecr
|
||||
def test_batch_delete_image_delete_last_tag():
|
||||
client = boto3.client('ecr', region_name='us-east-1')
|
||||
client.create_repository(
|
||||
repositoryName='test_repository'
|
||||
)
|
||||
|
||||
client.put_image(
|
||||
repositoryName='test_repository',
|
||||
imageManifest=json.dumps(_create_image_manifest()),
|
||||
imageTag='v1',
|
||||
)
|
||||
|
||||
describe_response1 = client.describe_images(repositoryName='test_repository')
|
||||
|
||||
batch_delete_response = client.batch_delete_image(
|
||||
registryId='012345678910',
|
||||
repositoryName='test_repository',
|
||||
imageIds=[
|
||||
{
|
||||
'imageTag': 'v1'
|
||||
},
|
||||
],
|
||||
)
|
||||
|
||||
describe_response2 = client.describe_images(repositoryName='test_repository')
|
||||
|
||||
type(describe_response1['imageDetails'][0]['imageTags']).should.be(list)
|
||||
len(describe_response1['imageDetails'][0]['imageTags']).should.be(1)
|
||||
|
||||
type(describe_response2['imageDetails']).should.be(list)
|
||||
len(describe_response2['imageDetails']).should.be(0)
|
||||
|
||||
type(batch_delete_response['imageIds']).should.be(list)
|
||||
len(batch_delete_response['imageIds']).should.be(1)
|
||||
|
||||
batch_delete_response['imageIds'][0]['imageTag'].should.equal("v1")
|
||||
|
||||
type(batch_delete_response['failures']).should.be(list)
|
||||
len(batch_delete_response['failures']).should.be(0)
|
||||
|
||||
|
||||
@mock_ecr
|
||||
def test_batch_delete_image_with_nonexistent_tag():
|
||||
client = boto3.client('ecr', region_name='us-east-1')
|
||||
_ = client.create_repository(
|
||||
client.create_repository(
|
||||
repositoryName='test_repository'
|
||||
)
|
||||
|
||||
@ -755,14 +836,13 @@ def test_batch_delete_image_with_nonexistent_tag():
|
||||
|
||||
tags = ['v1', 'v1.0', 'latest']
|
||||
for tag in tags:
|
||||
put_response = client.put_image(
|
||||
client.put_image(
|
||||
repositoryName='test_repository',
|
||||
imageManifest=json.dumps(manifest),
|
||||
imageTag=tag,
|
||||
)
|
||||
|
||||
describe_response = client.describe_images(repositoryName='test_repository')
|
||||
image_digest = describe_response['imageDetails'][0]['imageDigest']
|
||||
|
||||
missing_tag = "missing-tag"
|
||||
batch_delete_response = client.batch_delete_image(
|
||||
@ -792,7 +872,7 @@ def test_batch_delete_image_with_nonexistent_tag():
|
||||
@mock_ecr
|
||||
def test_batch_delete_image_by_digest():
|
||||
client = boto3.client('ecr', region_name='us-east-1')
|
||||
_ = client.create_repository(
|
||||
client.create_repository(
|
||||
repositoryName='test_repository'
|
||||
)
|
||||
|
||||
@ -800,7 +880,7 @@ def test_batch_delete_image_by_digest():
|
||||
|
||||
tags = ['v1', 'v2', 'latest']
|
||||
for tag in tags:
|
||||
put_response = client.put_image(
|
||||
client.put_image(
|
||||
repositoryName='test_repository',
|
||||
imageManifest=json.dumps(manifest),
|
||||
imageTag=tag
|
||||
@ -843,7 +923,7 @@ def test_batch_delete_image_by_digest():
|
||||
@mock_ecr
|
||||
def test_batch_delete_image_with_invalid_digest():
|
||||
client = boto3.client('ecr', region_name='us-east-1')
|
||||
_ = client.create_repository(
|
||||
client.create_repository(
|
||||
repositoryName='test_repository'
|
||||
)
|
||||
|
||||
@ -851,13 +931,12 @@ def test_batch_delete_image_with_invalid_digest():
|
||||
|
||||
tags = ['v1', 'v2', 'latest']
|
||||
for tag in tags:
|
||||
put_response = client.put_image(
|
||||
client.put_image(
|
||||
repositoryName='test_repository',
|
||||
imageManifest=json.dumps(manifest),
|
||||
imageTag=tag
|
||||
)
|
||||
|
||||
describe_response = client.describe_images(repositoryName='test_repository')
|
||||
invalid_image_digest = 'sha256:invalid-digest'
|
||||
|
||||
batch_delete_response = client.batch_delete_image(
|
||||
@ -884,7 +963,7 @@ def test_batch_delete_image_with_invalid_digest():
|
||||
@mock_ecr
|
||||
def test_batch_delete_image_with_missing_parameters():
|
||||
client = boto3.client('ecr', region_name='us-east-1')
|
||||
_ = client.create_repository(
|
||||
client.create_repository(
|
||||
repositoryName='test_repository'
|
||||
)
|
||||
|
||||
@ -910,7 +989,7 @@ def test_batch_delete_image_with_missing_parameters():
|
||||
@mock_ecr
|
||||
def test_batch_delete_image_with_matching_digest_and_tag():
|
||||
client = boto3.client('ecr', region_name='us-east-1')
|
||||
_ = client.create_repository(
|
||||
client.create_repository(
|
||||
repositoryName='test_repository'
|
||||
)
|
||||
|
||||
@ -918,7 +997,7 @@ def test_batch_delete_image_with_matching_digest_and_tag():
|
||||
|
||||
tags = ['v1', 'v1.0', 'latest']
|
||||
for tag in tags:
|
||||
put_response = client.put_image(
|
||||
client.put_image(
|
||||
repositoryName='test_repository',
|
||||
imageManifest=json.dumps(manifest),
|
||||
imageTag=tag
|
||||
@ -962,7 +1041,7 @@ def test_batch_delete_image_with_matching_digest_and_tag():
|
||||
@mock_ecr
|
||||
def test_batch_delete_image_with_mismatched_digest_and_tag():
|
||||
client = boto3.client('ecr', region_name='us-east-1')
|
||||
_ = client.create_repository(
|
||||
client.create_repository(
|
||||
repositoryName='test_repository'
|
||||
)
|
||||
|
||||
@ -970,7 +1049,7 @@ def test_batch_delete_image_with_mismatched_digest_and_tag():
|
||||
|
||||
tags = ['v1', 'latest']
|
||||
for tag in tags:
|
||||
put_response = client.put_image(
|
||||
client.put_image(
|
||||
repositoryName='test_repository',
|
||||
imageManifest=json.dumps(manifest),
|
||||
imageTag=tag
|
||||
|
@ -229,6 +229,26 @@ def test_delete_table():
|
||||
exc.exception.response['Error']['Code'].should.equal('EntityNotFoundException')
|
||||
exc.exception.response['Error']['Message'].should.match('Table myspecialtable not found')
|
||||
|
||||
@mock_glue
|
||||
def test_batch_delete_table():
|
||||
client = boto3.client('glue', region_name='us-east-1')
|
||||
database_name = 'myspecialdatabase'
|
||||
helpers.create_database(client, database_name)
|
||||
|
||||
table_name = 'myspecialtable'
|
||||
table_input = helpers.create_table_input(database_name, table_name)
|
||||
helpers.create_table(client, database_name, table_name, table_input)
|
||||
|
||||
result = client.batch_delete_table(DatabaseName=database_name, TablesToDelete=[table_name])
|
||||
result['ResponseMetadata']['HTTPStatusCode'].should.equal(200)
|
||||
|
||||
# confirm table is deleted
|
||||
with assert_raises(ClientError) as exc:
|
||||
helpers.get_table(client, database_name, table_name)
|
||||
|
||||
exc.exception.response['Error']['Code'].should.equal('EntityNotFoundException')
|
||||
exc.exception.response['Error']['Message'].should.match('Table myspecialtable not found')
|
||||
|
||||
|
||||
@mock_glue
|
||||
def test_get_partitions_empty():
|
||||
@ -310,6 +330,72 @@ def test_get_partition_not_found():
|
||||
exc.exception.response['Error']['Code'].should.equal('EntityNotFoundException')
|
||||
exc.exception.response['Error']['Message'].should.match('partition')
|
||||
|
||||
@mock_glue
|
||||
def test_batch_create_partition():
|
||||
client = boto3.client('glue', region_name='us-east-1')
|
||||
database_name = 'myspecialdatabase'
|
||||
table_name = 'myfirsttable'
|
||||
helpers.create_database(client, database_name)
|
||||
|
||||
helpers.create_table(client, database_name, table_name)
|
||||
|
||||
before = datetime.now(pytz.utc)
|
||||
|
||||
partition_inputs = []
|
||||
for i in range(0, 20):
|
||||
values = ["2018-10-{:2}".format(i)]
|
||||
part_input = helpers.create_partition_input(database_name, table_name, values=values)
|
||||
partition_inputs.append(part_input)
|
||||
|
||||
client.batch_create_partition(
|
||||
DatabaseName=database_name,
|
||||
TableName=table_name,
|
||||
PartitionInputList=partition_inputs
|
||||
)
|
||||
|
||||
after = datetime.now(pytz.utc)
|
||||
|
||||
response = client.get_partitions(DatabaseName=database_name, TableName=table_name)
|
||||
|
||||
partitions = response['Partitions']
|
||||
|
||||
partitions.should.have.length_of(20)
|
||||
|
||||
for idx, partition in enumerate(partitions):
|
||||
partition_input = partition_inputs[idx]
|
||||
|
||||
partition['TableName'].should.equal(table_name)
|
||||
partition['StorageDescriptor'].should.equal(partition_input['StorageDescriptor'])
|
||||
partition['Values'].should.equal(partition_input['Values'])
|
||||
partition['CreationTime'].should.be.greater_than(before)
|
||||
partition['CreationTime'].should.be.lower_than(after)
|
||||
|
||||
|
||||
@mock_glue
|
||||
def test_batch_create_partition_already_exist():
|
||||
client = boto3.client('glue', region_name='us-east-1')
|
||||
database_name = 'myspecialdatabase'
|
||||
table_name = 'myfirsttable'
|
||||
values = ['2018-10-01']
|
||||
helpers.create_database(client, database_name)
|
||||
|
||||
helpers.create_table(client, database_name, table_name)
|
||||
|
||||
helpers.create_partition(client, database_name, table_name, values=values)
|
||||
|
||||
partition_input = helpers.create_partition_input(database_name, table_name, values=values)
|
||||
|
||||
response = client.batch_create_partition(
|
||||
DatabaseName=database_name,
|
||||
TableName=table_name,
|
||||
PartitionInputList=[partition_input]
|
||||
)
|
||||
|
||||
response.should.have.key('Errors')
|
||||
response['Errors'].should.have.length_of(1)
|
||||
response['Errors'][0]['PartitionValues'].should.equal(values)
|
||||
response['Errors'][0]['ErrorDetail']['ErrorCode'].should.equal('AlreadyExistsException')
|
||||
|
||||
|
||||
@mock_glue
|
||||
def test_get_partition():
|
||||
@ -445,3 +531,112 @@ def test_update_partition_move():
|
||||
|
||||
partition['TableName'].should.equal(table_name)
|
||||
partition['StorageDescriptor']['Columns'].should.equal([{'Name': 'country', 'Type': 'string'}])
|
||||
|
||||
@mock_glue
|
||||
def test_delete_partition():
|
||||
client = boto3.client('glue', region_name='us-east-1')
|
||||
database_name = 'myspecialdatabase'
|
||||
table_name = 'myfirsttable'
|
||||
values = ['2018-10-01']
|
||||
helpers.create_database(client, database_name)
|
||||
helpers.create_table(client, database_name, table_name)
|
||||
|
||||
part_input = helpers.create_partition_input(database_name, table_name, values=values)
|
||||
helpers.create_partition(client, database_name, table_name, part_input)
|
||||
|
||||
client.delete_partition(
|
||||
DatabaseName=database_name,
|
||||
TableName=table_name,
|
||||
PartitionValues=values,
|
||||
)
|
||||
|
||||
response = client.get_partitions(DatabaseName=database_name, TableName=table_name)
|
||||
partitions = response['Partitions']
|
||||
partitions.should.be.empty
|
||||
|
||||
@mock_glue
|
||||
def test_delete_partition_bad_partition():
|
||||
client = boto3.client('glue', region_name='us-east-1')
|
||||
database_name = 'myspecialdatabase'
|
||||
table_name = 'myfirsttable'
|
||||
values = ['2018-10-01']
|
||||
helpers.create_database(client, database_name)
|
||||
helpers.create_table(client, database_name, table_name)
|
||||
|
||||
with assert_raises(ClientError) as exc:
|
||||
client.delete_partition(
|
||||
DatabaseName=database_name,
|
||||
TableName=table_name,
|
||||
PartitionValues=values,
|
||||
)
|
||||
|
||||
exc.exception.response['Error']['Code'].should.equal('EntityNotFoundException')
|
||||
|
||||
@mock_glue
|
||||
def test_batch_delete_partition():
|
||||
client = boto3.client('glue', region_name='us-east-1')
|
||||
database_name = 'myspecialdatabase'
|
||||
table_name = 'myfirsttable'
|
||||
helpers.create_database(client, database_name)
|
||||
helpers.create_table(client, database_name, table_name)
|
||||
|
||||
partition_inputs = []
|
||||
for i in range(0, 20):
|
||||
values = ["2018-10-{:2}".format(i)]
|
||||
part_input = helpers.create_partition_input(database_name, table_name, values=values)
|
||||
partition_inputs.append(part_input)
|
||||
|
||||
client.batch_create_partition(
|
||||
DatabaseName=database_name,
|
||||
TableName=table_name,
|
||||
PartitionInputList=partition_inputs
|
||||
)
|
||||
|
||||
partition_values = [{"Values": p["Values"]} for p in partition_inputs]
|
||||
|
||||
response = client.batch_delete_partition(
|
||||
DatabaseName=database_name,
|
||||
TableName=table_name,
|
||||
PartitionsToDelete=partition_values,
|
||||
)
|
||||
|
||||
response.should_not.have.key('Errors')
|
||||
|
||||
@mock_glue
|
||||
def test_batch_delete_partition_with_bad_partitions():
|
||||
client = boto3.client('glue', region_name='us-east-1')
|
||||
database_name = 'myspecialdatabase'
|
||||
table_name = 'myfirsttable'
|
||||
helpers.create_database(client, database_name)
|
||||
helpers.create_table(client, database_name, table_name)
|
||||
|
||||
partition_inputs = []
|
||||
for i in range(0, 20):
|
||||
values = ["2018-10-{:2}".format(i)]
|
||||
part_input = helpers.create_partition_input(database_name, table_name, values=values)
|
||||
partition_inputs.append(part_input)
|
||||
|
||||
client.batch_create_partition(
|
||||
DatabaseName=database_name,
|
||||
TableName=table_name,
|
||||
PartitionInputList=partition_inputs
|
||||
)
|
||||
|
||||
partition_values = [{"Values": p["Values"]} for p in partition_inputs]
|
||||
|
||||
partition_values.insert(5, {"Values": ["2018-11-01"]})
|
||||
partition_values.insert(10, {"Values": ["2018-11-02"]})
|
||||
partition_values.insert(15, {"Values": ["2018-11-03"]})
|
||||
|
||||
response = client.batch_delete_partition(
|
||||
DatabaseName=database_name,
|
||||
TableName=table_name,
|
||||
PartitionsToDelete=partition_values,
|
||||
)
|
||||
|
||||
response.should.have.key('Errors')
|
||||
response['Errors'].should.have.length_of(3)
|
||||
error_partitions = map(lambda x: x['PartitionValues'], response['Errors'])
|
||||
['2018-11-01'].should.be.within(error_partitions)
|
||||
['2018-11-02'].should.be.within(error_partitions)
|
||||
['2018-11-03'].should.be.within(error_partitions)
|
||||
|
@ -1,5 +1,6 @@
|
||||
from __future__ import unicode_literals
|
||||
import base64
|
||||
import json
|
||||
|
||||
import boto
|
||||
import boto3
|
||||
@ -29,6 +30,44 @@ FyDHrtlrS80dPUQWNYHw++oACDpWO01LGLPPrGmuO/7cOdojPEd852q5gd+7W9xt
|
||||
8vUH+pBa6IBLbvBp+szli51V3TLSWcoyy4ceJNQU2vCkTLoFdS0RLd/7tQ==
|
||||
-----END CERTIFICATE-----"""
|
||||
|
||||
MOCK_POLICY = """
|
||||
{
|
||||
"Version": "2012-10-17",
|
||||
"Statement":
|
||||
{
|
||||
"Effect": "Allow",
|
||||
"Action": "s3:ListBucket",
|
||||
"Resource": "arn:aws:s3:::example_bucket"
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
MOCK_POLICY_2 = """
|
||||
{
|
||||
"Version": "2012-10-17",
|
||||
"Id": "2",
|
||||
"Statement":
|
||||
{
|
||||
"Effect": "Allow",
|
||||
"Action": "s3:ListBucket",
|
||||
"Resource": "arn:aws:s3:::example_bucket"
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
MOCK_POLICY_3 = """
|
||||
{
|
||||
"Version": "2012-10-17",
|
||||
"Id": "3",
|
||||
"Statement":
|
||||
{
|
||||
"Effect": "Allow",
|
||||
"Action": "s3:ListBucket",
|
||||
"Resource": "arn:aws:s3:::example_bucket"
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
|
||||
@mock_iam_deprecated()
|
||||
def test_get_all_server_certs():
|
||||
@ -243,12 +282,12 @@ def test_list_instance_profiles_for_role():
|
||||
def test_list_role_policies():
|
||||
conn = boto.connect_iam()
|
||||
conn.create_role("my-role")
|
||||
conn.put_role_policy("my-role", "test policy", "my policy")
|
||||
conn.put_role_policy("my-role", "test policy", MOCK_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")
|
||||
conn.put_role_policy("my-role", "test policy 2", MOCK_POLICY)
|
||||
role = conn.list_role_policies("my-role")
|
||||
role.policy_names.should.have.length_of(2)
|
||||
|
||||
@ -266,12 +305,21 @@ def test_put_role_policy():
|
||||
conn = boto.connect_iam()
|
||||
conn.create_role(
|
||||
"my-role", assume_role_policy_document="some policy", path="my-path")
|
||||
conn.put_role_policy("my-role", "test policy", "my policy")
|
||||
conn.put_role_policy("my-role", "test policy", MOCK_POLICY)
|
||||
policy = conn.get_role_policy(
|
||||
"my-role", "test policy")['get_role_policy_response']['get_role_policy_result']['policy_name']
|
||||
policy.should.equal("test policy")
|
||||
|
||||
|
||||
@mock_iam
|
||||
def test_get_role_policy():
|
||||
conn = boto3.client('iam', region_name='us-east-1')
|
||||
conn.create_role(
|
||||
RoleName="my-role", AssumeRolePolicyDocument="some policy", Path="my-path")
|
||||
with assert_raises(conn.exceptions.NoSuchEntityException):
|
||||
conn.get_role_policy(RoleName="my-role", PolicyName="does-not-exist")
|
||||
|
||||
|
||||
@mock_iam_deprecated()
|
||||
def test_update_assume_role_policy():
|
||||
conn = boto.connect_iam()
|
||||
@ -286,7 +334,7 @@ def test_create_policy():
|
||||
conn = boto3.client('iam', region_name='us-east-1')
|
||||
response = conn.create_policy(
|
||||
PolicyName="TestCreatePolicy",
|
||||
PolicyDocument='{"some":"policy"}')
|
||||
PolicyDocument=MOCK_POLICY)
|
||||
response['Policy']['Arn'].should.equal("arn:aws:iam::123456789012:policy/TestCreatePolicy")
|
||||
|
||||
|
||||
@ -299,20 +347,62 @@ def test_create_policy_versions():
|
||||
PolicyDocument='{"some":"policy"}')
|
||||
conn.create_policy(
|
||||
PolicyName="TestCreatePolicyVersion",
|
||||
PolicyDocument='{"some":"policy"}')
|
||||
PolicyDocument=MOCK_POLICY)
|
||||
version = conn.create_policy_version(
|
||||
PolicyArn="arn:aws:iam::123456789012:policy/TestCreatePolicyVersion",
|
||||
PolicyDocument='{"some":"policy"}',
|
||||
PolicyDocument=MOCK_POLICY,
|
||||
SetAsDefault=True)
|
||||
version.get('PolicyVersion').get('Document').should.equal({'some': 'policy'})
|
||||
version.get('PolicyVersion').get('Document').should.equal(json.loads(MOCK_POLICY))
|
||||
version.get('PolicyVersion').get('VersionId').should.equal("v2")
|
||||
version.get('PolicyVersion').get('IsDefaultVersion').should.be.ok
|
||||
conn.delete_policy_version(
|
||||
PolicyArn="arn:aws:iam::123456789012:policy/TestCreatePolicyVersion",
|
||||
VersionId="v1")
|
||||
version = conn.create_policy_version(
|
||||
PolicyArn="arn:aws:iam::123456789012:policy/TestCreatePolicyVersion",
|
||||
PolicyDocument='{"some":"policy"}')
|
||||
PolicyDocument=MOCK_POLICY)
|
||||
version.get('PolicyVersion').get('VersionId').should.equal("v3")
|
||||
version.get('PolicyVersion').get('IsDefaultVersion').shouldnt.be.ok
|
||||
|
||||
|
||||
@mock_iam
|
||||
def test_create_many_policy_versions():
|
||||
conn = boto3.client('iam', region_name='us-east-1')
|
||||
conn.create_policy(
|
||||
PolicyName="TestCreateManyPolicyVersions",
|
||||
PolicyDocument=MOCK_POLICY)
|
||||
for _ in range(0, 4):
|
||||
conn.create_policy_version(
|
||||
PolicyArn="arn:aws:iam::123456789012:policy/TestCreateManyPolicyVersions",
|
||||
PolicyDocument=MOCK_POLICY)
|
||||
with assert_raises(ClientError):
|
||||
conn.create_policy_version(
|
||||
PolicyArn="arn:aws:iam::123456789012:policy/TestCreateManyPolicyVersions",
|
||||
PolicyDocument=MOCK_POLICY)
|
||||
|
||||
|
||||
@mock_iam
|
||||
def test_set_default_policy_version():
|
||||
conn = boto3.client('iam', region_name='us-east-1')
|
||||
conn.create_policy(
|
||||
PolicyName="TestSetDefaultPolicyVersion",
|
||||
PolicyDocument=MOCK_POLICY)
|
||||
conn.create_policy_version(
|
||||
PolicyArn="arn:aws:iam::123456789012:policy/TestSetDefaultPolicyVersion",
|
||||
PolicyDocument=MOCK_POLICY_2,
|
||||
SetAsDefault=True)
|
||||
conn.create_policy_version(
|
||||
PolicyArn="arn:aws:iam::123456789012:policy/TestSetDefaultPolicyVersion",
|
||||
PolicyDocument=MOCK_POLICY_3,
|
||||
SetAsDefault=True)
|
||||
versions = conn.list_policy_versions(
|
||||
PolicyArn="arn:aws:iam::123456789012:policy/TestSetDefaultPolicyVersion")
|
||||
versions.get('Versions')[0].get('Document').should.equal(json.loads(MOCK_POLICY))
|
||||
versions.get('Versions')[0].get('IsDefaultVersion').shouldnt.be.ok
|
||||
versions.get('Versions')[1].get('Document').should.equal(json.loads(MOCK_POLICY_2))
|
||||
versions.get('Versions')[1].get('IsDefaultVersion').shouldnt.be.ok
|
||||
versions.get('Versions')[2].get('Document').should.equal(json.loads(MOCK_POLICY_3))
|
||||
versions.get('Versions')[2].get('IsDefaultVersion').should.be.ok
|
||||
|
||||
|
||||
@mock_iam
|
||||
@ -320,10 +410,21 @@ def test_get_policy():
|
||||
conn = boto3.client('iam', region_name='us-east-1')
|
||||
response = conn.create_policy(
|
||||
PolicyName="TestGetPolicy",
|
||||
PolicyDocument='{"some":"policy"}')
|
||||
PolicyDocument=MOCK_POLICY)
|
||||
policy = conn.get_policy(
|
||||
PolicyArn="arn:aws:iam::123456789012:policy/TestGetPolicy")
|
||||
response['Policy']['Arn'].should.equal("arn:aws:iam::123456789012:policy/TestGetPolicy")
|
||||
policy['Policy']['Arn'].should.equal("arn:aws:iam::123456789012:policy/TestGetPolicy")
|
||||
|
||||
|
||||
@mock_iam
|
||||
def test_get_aws_managed_policy():
|
||||
conn = boto3.client('iam', region_name='us-east-1')
|
||||
managed_policy_arn = 'arn:aws:iam::aws:policy/IAMUserChangePassword'
|
||||
managed_policy_create_date = datetime.strptime("2016-11-15T00:25:16+00:00", "%Y-%m-%dT%H:%M:%S+00:00")
|
||||
policy = conn.get_policy(
|
||||
PolicyArn=managed_policy_arn)
|
||||
policy['Policy']['Arn'].should.equal(managed_policy_arn)
|
||||
policy['Policy']['CreateDate'].replace(tzinfo=None).should.equal(managed_policy_create_date)
|
||||
|
||||
|
||||
@mock_iam
|
||||
@ -331,10 +432,10 @@ def test_get_policy_version():
|
||||
conn = boto3.client('iam', region_name='us-east-1')
|
||||
conn.create_policy(
|
||||
PolicyName="TestGetPolicyVersion",
|
||||
PolicyDocument='{"some":"policy"}')
|
||||
PolicyDocument=MOCK_POLICY)
|
||||
version = conn.create_policy_version(
|
||||
PolicyArn="arn:aws:iam::123456789012:policy/TestGetPolicyVersion",
|
||||
PolicyDocument='{"some":"policy"}')
|
||||
PolicyDocument=MOCK_POLICY)
|
||||
with assert_raises(ClientError):
|
||||
conn.get_policy_version(
|
||||
PolicyArn="arn:aws:iam::123456789012:policy/TestGetPolicyVersion",
|
||||
@ -342,7 +443,40 @@ def test_get_policy_version():
|
||||
retrieved = conn.get_policy_version(
|
||||
PolicyArn="arn:aws:iam::123456789012:policy/TestGetPolicyVersion",
|
||||
VersionId=version.get('PolicyVersion').get('VersionId'))
|
||||
retrieved.get('PolicyVersion').get('Document').should.equal({'some': 'policy'})
|
||||
retrieved.get('PolicyVersion').get('Document').should.equal(json.loads(MOCK_POLICY))
|
||||
retrieved.get('PolicyVersion').get('IsDefaultVersion').shouldnt.be.ok
|
||||
|
||||
|
||||
@mock_iam
|
||||
def test_get_aws_managed_policy_version():
|
||||
conn = boto3.client('iam', region_name='us-east-1')
|
||||
managed_policy_arn = 'arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole'
|
||||
managed_policy_version_create_date = datetime.strptime("2015-04-09T15:03:43+00:00", "%Y-%m-%dT%H:%M:%S+00:00")
|
||||
with assert_raises(ClientError):
|
||||
conn.get_policy_version(
|
||||
PolicyArn=managed_policy_arn,
|
||||
VersionId='v2-does-not-exist')
|
||||
retrieved = conn.get_policy_version(
|
||||
PolicyArn=managed_policy_arn,
|
||||
VersionId="v1")
|
||||
retrieved['PolicyVersion']['CreateDate'].replace(tzinfo=None).should.equal(managed_policy_version_create_date)
|
||||
retrieved['PolicyVersion']['Document'].should.be.an(dict)
|
||||
|
||||
|
||||
@mock_iam
|
||||
def test_get_aws_managed_policy_v4_version():
|
||||
conn = boto3.client('iam', region_name='us-east-1')
|
||||
managed_policy_arn = 'arn:aws:iam::aws:policy/job-function/SystemAdministrator'
|
||||
managed_policy_version_create_date = datetime.strptime("2018-10-08T21:33:45+00:00", "%Y-%m-%dT%H:%M:%S+00:00")
|
||||
with assert_raises(ClientError):
|
||||
conn.get_policy_version(
|
||||
PolicyArn=managed_policy_arn,
|
||||
VersionId='v2-does-not-exist')
|
||||
retrieved = conn.get_policy_version(
|
||||
PolicyArn=managed_policy_arn,
|
||||
VersionId="v4")
|
||||
retrieved['PolicyVersion']['CreateDate'].replace(tzinfo=None).should.equal(managed_policy_version_create_date)
|
||||
retrieved['PolicyVersion']['Document'].should.be.an(dict)
|
||||
|
||||
|
||||
@mock_iam
|
||||
@ -353,22 +487,24 @@ def test_list_policy_versions():
|
||||
PolicyArn="arn:aws:iam::123456789012:policy/TestListPolicyVersions")
|
||||
conn.create_policy(
|
||||
PolicyName="TestListPolicyVersions",
|
||||
PolicyDocument='{"first":"policy"}')
|
||||
PolicyDocument=MOCK_POLICY)
|
||||
versions = conn.list_policy_versions(
|
||||
PolicyArn="arn:aws:iam::123456789012:policy/TestListPolicyVersions")
|
||||
versions.get('Versions')[0].get('VersionId').should.equal('v1')
|
||||
versions.get('Versions')[0].get('IsDefaultVersion').should.be.ok
|
||||
|
||||
conn.create_policy_version(
|
||||
PolicyArn="arn:aws:iam::123456789012:policy/TestListPolicyVersions",
|
||||
PolicyDocument='{"second":"policy"}')
|
||||
PolicyDocument=MOCK_POLICY_2)
|
||||
conn.create_policy_version(
|
||||
PolicyArn="arn:aws:iam::123456789012:policy/TestListPolicyVersions",
|
||||
PolicyDocument='{"third":"policy"}')
|
||||
PolicyDocument=MOCK_POLICY_3)
|
||||
versions = conn.list_policy_versions(
|
||||
PolicyArn="arn:aws:iam::123456789012:policy/TestListPolicyVersions")
|
||||
print(versions.get('Versions'))
|
||||
versions.get('Versions')[1].get('Document').should.equal({'second': 'policy'})
|
||||
versions.get('Versions')[2].get('Document').should.equal({'third': 'policy'})
|
||||
versions.get('Versions')[1].get('Document').should.equal(json.loads(MOCK_POLICY_2))
|
||||
versions.get('Versions')[1].get('IsDefaultVersion').shouldnt.be.ok
|
||||
versions.get('Versions')[2].get('Document').should.equal(json.loads(MOCK_POLICY_3))
|
||||
versions.get('Versions')[2].get('IsDefaultVersion').shouldnt.be.ok
|
||||
|
||||
|
||||
@mock_iam
|
||||
@ -376,10 +512,10 @@ def test_delete_policy_version():
|
||||
conn = boto3.client('iam', region_name='us-east-1')
|
||||
conn.create_policy(
|
||||
PolicyName="TestDeletePolicyVersion",
|
||||
PolicyDocument='{"first":"policy"}')
|
||||
PolicyDocument=MOCK_POLICY)
|
||||
conn.create_policy_version(
|
||||
PolicyArn="arn:aws:iam::123456789012:policy/TestDeletePolicyVersion",
|
||||
PolicyDocument='{"second":"policy"}')
|
||||
PolicyDocument=MOCK_POLICY)
|
||||
with assert_raises(ClientError):
|
||||
conn.delete_policy_version(
|
||||
PolicyArn="arn:aws:iam::123456789012:policy/TestDeletePolicyVersion",
|
||||
@ -392,6 +528,21 @@ def test_delete_policy_version():
|
||||
len(versions.get('Versions')).should.equal(1)
|
||||
|
||||
|
||||
@mock_iam
|
||||
def test_delete_default_policy_version():
|
||||
conn = boto3.client('iam', region_name='us-east-1')
|
||||
conn.create_policy(
|
||||
PolicyName="TestDeletePolicyVersion",
|
||||
PolicyDocument=MOCK_POLICY)
|
||||
conn.create_policy_version(
|
||||
PolicyArn="arn:aws:iam::123456789012:policy/TestDeletePolicyVersion",
|
||||
PolicyDocument=MOCK_POLICY_2)
|
||||
with assert_raises(ClientError):
|
||||
conn.delete_policy_version(
|
||||
PolicyArn="arn:aws:iam::123456789012:policy/TestDeletePolicyVersion",
|
||||
VersionId='v1')
|
||||
|
||||
|
||||
@mock_iam_deprecated()
|
||||
def test_create_user():
|
||||
conn = boto.connect_iam()
|
||||
@ -446,22 +597,20 @@ def test_list_users():
|
||||
@mock_iam()
|
||||
def test_user_policies():
|
||||
policy_name = 'UserManagedPolicy'
|
||||
policy_document = "{'mypolicy': 'test'}"
|
||||
user_name = 'my-user'
|
||||
conn = boto3.client('iam', region_name='us-east-1')
|
||||
conn.create_user(UserName=user_name)
|
||||
conn.put_user_policy(
|
||||
UserName=user_name,
|
||||
PolicyName=policy_name,
|
||||
PolicyDocument=policy_document
|
||||
PolicyDocument=MOCK_POLICY
|
||||
)
|
||||
|
||||
policy_doc = conn.get_user_policy(
|
||||
UserName=user_name,
|
||||
PolicyName=policy_name
|
||||
)
|
||||
test = policy_document in policy_doc['PolicyDocument']
|
||||
test.should.equal(True)
|
||||
policy_doc['PolicyDocument'].should.equal(json.loads(MOCK_POLICY))
|
||||
|
||||
policies = conn.list_user_policies(UserName=user_name)
|
||||
len(policies['PolicyNames']).should.equal(1)
|
||||
@ -497,13 +646,17 @@ def test_delete_login_profile():
|
||||
conn.delete_login_profile('my-user')
|
||||
|
||||
|
||||
@mock_iam_deprecated()
|
||||
@mock_iam()
|
||||
def test_create_access_key():
|
||||
conn = boto.connect_iam()
|
||||
with assert_raises(BotoServerError):
|
||||
conn.create_access_key('my-user')
|
||||
conn.create_user('my-user')
|
||||
conn.create_access_key('my-user')
|
||||
conn = boto3.client('iam', region_name='us-east-1')
|
||||
with assert_raises(ClientError):
|
||||
conn.create_access_key(UserName='my-user')
|
||||
conn.create_user(UserName='my-user')
|
||||
access_key = conn.create_access_key(UserName='my-user')["AccessKey"]
|
||||
(datetime.utcnow() - access_key["CreateDate"].replace(tzinfo=None)).seconds.should.be.within(0, 10)
|
||||
access_key["AccessKeyId"].should.have.length_of(20)
|
||||
access_key["SecretAccessKey"].should.have.length_of(40)
|
||||
assert access_key["AccessKeyId"].startswith("AKIA")
|
||||
|
||||
|
||||
@mock_iam_deprecated()
|
||||
@ -622,7 +775,7 @@ def test_managed_policy():
|
||||
conn = boto.connect_iam()
|
||||
|
||||
conn.create_policy(policy_name='UserManagedPolicy',
|
||||
policy_document={'mypolicy': 'test'},
|
||||
policy_document=MOCK_POLICY,
|
||||
path='/mypolicy/',
|
||||
description='my user managed policy')
|
||||
|
||||
@ -723,7 +876,7 @@ def test_attach_detach_user_policy():
|
||||
|
||||
policy_name = 'UserAttachedPolicy'
|
||||
policy = iam.create_policy(PolicyName=policy_name,
|
||||
PolicyDocument='{"mypolicy": "test"}',
|
||||
PolicyDocument=MOCK_POLICY,
|
||||
Path='/mypolicy/',
|
||||
Description='my user attached policy')
|
||||
|
||||
@ -779,7 +932,6 @@ def test_get_access_key_last_used():
|
||||
|
||||
@mock_iam
|
||||
def test_get_account_authorization_details():
|
||||
import json
|
||||
test_policy = json.dumps({
|
||||
"Version": "2012-10-17",
|
||||
"Statement": [
|
||||
@ -1211,7 +1363,6 @@ def test_update_role():
|
||||
|
||||
@mock_iam()
|
||||
def test_list_entities_for_policy():
|
||||
import json
|
||||
test_policy = json.dumps({
|
||||
"Version": "2012-10-17",
|
||||
"Statement": [
|
||||
|
@ -10,6 +10,18 @@ from nose.tools import assert_raises
|
||||
from boto.exception import BotoServerError
|
||||
from moto import mock_iam, mock_iam_deprecated
|
||||
|
||||
MOCK_POLICY = """
|
||||
{
|
||||
"Version": "2012-10-17",
|
||||
"Statement":
|
||||
{
|
||||
"Effect": "Allow",
|
||||
"Action": "s3:ListBucket",
|
||||
"Resource": "arn:aws:s3:::example_bucket"
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
|
||||
@mock_iam_deprecated()
|
||||
def test_create_group():
|
||||
@ -101,7 +113,7 @@ def test_get_groups_for_user():
|
||||
def test_put_group_policy():
|
||||
conn = boto.connect_iam()
|
||||
conn.create_group('my-group')
|
||||
conn.put_group_policy('my-group', 'my-policy', '{"some": "json"}')
|
||||
conn.put_group_policy('my-group', 'my-policy', MOCK_POLICY)
|
||||
|
||||
|
||||
@mock_iam
|
||||
@ -131,7 +143,7 @@ def test_get_group_policy():
|
||||
with assert_raises(BotoServerError):
|
||||
conn.get_group_policy('my-group', 'my-policy')
|
||||
|
||||
conn.put_group_policy('my-group', 'my-policy', '{"some": "json"}')
|
||||
conn.put_group_policy('my-group', 'my-policy', MOCK_POLICY)
|
||||
conn.get_group_policy('my-group', 'my-policy')
|
||||
|
||||
|
||||
@ -141,7 +153,7 @@ def test_get_all_group_policies():
|
||||
conn.create_group('my-group')
|
||||
policies = conn.get_all_group_policies('my-group')['list_group_policies_response']['list_group_policies_result']['policy_names']
|
||||
assert policies == []
|
||||
conn.put_group_policy('my-group', 'my-policy', '{"some": "json"}')
|
||||
conn.put_group_policy('my-group', 'my-policy', MOCK_POLICY)
|
||||
policies = conn.get_all_group_policies('my-group')['list_group_policies_response']['list_group_policies_result']['policy_names']
|
||||
assert policies == ['my-policy']
|
||||
|
||||
@ -151,5 +163,5 @@ def test_list_group_policies():
|
||||
conn = boto3.client('iam', region_name='us-east-1')
|
||||
conn.create_group(GroupName='my-group')
|
||||
conn.list_group_policies(GroupName='my-group')['PolicyNames'].should.be.empty
|
||||
conn.put_group_policy(GroupName='my-group', PolicyName='my-policy', PolicyDocument='{"some": "json"}')
|
||||
conn.put_group_policy(GroupName='my-group', PolicyName='my-policy', PolicyDocument=MOCK_POLICY)
|
||||
conn.list_group_policies(GroupName='my-group')['PolicyNames'].should.equal(['my-policy'])
|
||||
|
1861
tests/test_iam/test_iam_policies.py
Normal file
1861
tests/test_iam/test_iam_policies.py
Normal file
File diff suppressed because it is too large
Load Diff
@ -15,7 +15,7 @@ from moto import mock_kinesis, mock_kinesis_deprecated
|
||||
def test_create_cluster():
|
||||
conn = boto.kinesis.connect_to_region("us-west-2")
|
||||
|
||||
conn.create_stream("my_stream", 2)
|
||||
conn.create_stream("my_stream", 3)
|
||||
|
||||
stream_response = conn.describe_stream("my_stream")
|
||||
|
||||
@ -27,7 +27,7 @@ def test_create_cluster():
|
||||
stream["StreamStatus"].should.equal("ACTIVE")
|
||||
|
||||
shards = stream['Shards']
|
||||
shards.should.have.length_of(2)
|
||||
shards.should.have.length_of(3)
|
||||
|
||||
|
||||
@mock_kinesis_deprecated
|
||||
|
@ -110,6 +110,7 @@ def test_rrset():
|
||||
|
||||
changes = ResourceRecordSets(conn, zoneid)
|
||||
changes.add_change("DELETE", "foo.bar.testdns.aws.com", "A")
|
||||
changes.add_change("DELETE", "foo.bar.testdns.aws.com", "TXT")
|
||||
changes.commit()
|
||||
|
||||
changes = ResourceRecordSets(conn, zoneid)
|
||||
@ -123,12 +124,12 @@ def test_rrset():
|
||||
rrsets.should.have.length_of(2)
|
||||
|
||||
rrsets = conn.get_all_rrsets(
|
||||
zoneid, name="foo.bar.testdns.aws.com", type="A")
|
||||
zoneid, name="bar.foo.testdns.aws.com", type="A")
|
||||
rrsets.should.have.length_of(1)
|
||||
rrsets[0].resource_records[0].should.equal('1.2.3.4')
|
||||
rrsets[0].resource_records[0].should.equal('5.6.7.8')
|
||||
|
||||
rrsets = conn.get_all_rrsets(
|
||||
zoneid, name="bar.foo.testdns.aws.com", type="A")
|
||||
zoneid, name="foo.bar.testdns.aws.com", type="A")
|
||||
rrsets.should.have.length_of(2)
|
||||
resource_records = [rr for rr_set in rrsets for rr in rr_set.resource_records]
|
||||
resource_records.should.contain('1.2.3.4')
|
||||
@ -617,7 +618,7 @@ def test_change_resource_record_sets_crud_valid():
|
||||
})
|
||||
cname_alias_record_detail.should_not.contain('ResourceRecords')
|
||||
|
||||
# Delete record.
|
||||
# Delete record with wrong type.
|
||||
delete_payload = {
|
||||
'Comment': 'delete prod.redis.db',
|
||||
'Changes': [
|
||||
@ -632,6 +633,23 @@ def test_change_resource_record_sets_crud_valid():
|
||||
}
|
||||
conn.change_resource_record_sets(HostedZoneId=hosted_zone_id, ChangeBatch=delete_payload)
|
||||
response = conn.list_resource_record_sets(HostedZoneId=hosted_zone_id)
|
||||
len(response['ResourceRecordSets']).should.equal(1)
|
||||
|
||||
# Delete record.
|
||||
delete_payload = {
|
||||
'Comment': 'delete prod.redis.db',
|
||||
'Changes': [
|
||||
{
|
||||
'Action': 'DELETE',
|
||||
'ResourceRecordSet': {
|
||||
'Name': 'prod.redis.db',
|
||||
'Type': 'A',
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
conn.change_resource_record_sets(HostedZoneId=hosted_zone_id, ChangeBatch=delete_payload)
|
||||
response = conn.list_resource_record_sets(HostedZoneId=hosted_zone_id)
|
||||
len(response['ResourceRecordSets']).should.equal(0)
|
||||
|
||||
|
||||
|
@ -32,6 +32,18 @@ def test_create_and_delete_topic():
|
||||
topics = topics_json["Topics"]
|
||||
topics.should.have.length_of(0)
|
||||
|
||||
|
||||
@mock_sns
|
||||
def test_create_topic_with_attributes():
|
||||
conn = boto3.client("sns", region_name="us-east-1")
|
||||
conn.create_topic(Name='some-topic-with-attribute', Attributes={'DisplayName': 'test-topic'})
|
||||
topics_json = conn.list_topics()
|
||||
topic_arn = topics_json["Topics"][0]['TopicArn']
|
||||
|
||||
attributes = conn.get_topic_attributes(TopicArn=topic_arn)['Attributes']
|
||||
attributes['DisplayName'].should.equal('test-topic')
|
||||
|
||||
|
||||
@mock_sns
|
||||
def test_create_topic_should_be_indempodent():
|
||||
conn = boto3.client("sns", region_name="us-east-1")
|
||||
|
118
update_version_from_git.py
Normal file
118
update_version_from_git.py
Normal file
@ -0,0 +1,118 @@
|
||||
"""
|
||||
Adapted from https://github.com/pygame/pygameweb/blob/master/pygameweb/builds/update_version_from_git.py
|
||||
|
||||
For updating the version from git.
|
||||
__init__.py contains a __version__ field.
|
||||
Update that.
|
||||
If we are on master, we want to update the version as a pre-release.
|
||||
git describe --tags
|
||||
With these:
|
||||
__init__.py
|
||||
__version__= '0.0.2'
|
||||
git describe --tags
|
||||
0.0.1-22-g729a5ae
|
||||
We want this:
|
||||
__init__.py
|
||||
__version__= '0.0.2.dev22.g729a5ae'
|
||||
Get the branch/tag name with this.
|
||||
git symbolic-ref -q --short HEAD || git describe --tags --exact-match
|
||||
"""
|
||||
|
||||
import io
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
|
||||
|
||||
def migrate_source_attribute(attr, to_this, target_file, regex):
|
||||
"""Updates __magic__ attributes in the source file"""
|
||||
change_this = re.compile(regex, re.S)
|
||||
new_file = []
|
||||
found = False
|
||||
|
||||
with open(target_file, 'r') as fp:
|
||||
lines = fp.readlines()
|
||||
|
||||
for line in lines:
|
||||
if line.startswith(attr):
|
||||
found = True
|
||||
line = re.sub(change_this, to_this, line)
|
||||
new_file.append(line)
|
||||
|
||||
if found:
|
||||
with open(target_file, 'w') as fp:
|
||||
fp.writelines(new_file)
|
||||
|
||||
def migrate_version(target_file, new_version):
|
||||
"""Updates __version__ in the source file"""
|
||||
regex = r"['\"](.*)['\"]"
|
||||
migrate_source_attribute('__version__', "'{new_version}'".format(new_version=new_version), target_file, regex)
|
||||
|
||||
|
||||
def is_master_branch():
|
||||
cmd = ('git rev-parse --abbrev-ref HEAD')
|
||||
tag_branch = subprocess.check_output(cmd, shell=True)
|
||||
return tag_branch in [b'master\n']
|
||||
|
||||
def git_tag_name():
|
||||
cmd = ('git describe --tags')
|
||||
tag_branch = subprocess.check_output(cmd, shell=True)
|
||||
tag_branch = tag_branch.decode().strip()
|
||||
return tag_branch
|
||||
|
||||
def get_git_version_info():
|
||||
cmd = 'git describe --tags'
|
||||
ver_str = subprocess.check_output(cmd, shell=True)
|
||||
ver, commits_since, githash = ver_str.decode().strip().split('-')
|
||||
return ver, commits_since, githash
|
||||
|
||||
def prerelease_version():
|
||||
""" return what the prerelease version should be.
|
||||
https://packaging.python.org/tutorials/distributing-packages/#pre-release-versioning
|
||||
0.0.2.dev22
|
||||
"""
|
||||
ver, commits_since, githash = get_git_version_info()
|
||||
initpy_ver = get_version()
|
||||
|
||||
assert len(initpy_ver.split('.')) in [3, 4], 'moto/__init__.py version should be like 0.0.2 or 0.0.2.dev'
|
||||
assert initpy_ver > ver, 'the moto/__init__.py version should be newer than the last tagged release.'
|
||||
return '{initpy_ver}.dev{commits_since}'.format(initpy_ver=initpy_ver, commits_since=commits_since)
|
||||
|
||||
def read(*parts):
|
||||
""" Reads in file from *parts.
|
||||
"""
|
||||
try:
|
||||
return io.open(os.path.join(*parts), 'r', encoding='utf-8').read()
|
||||
except IOError:
|
||||
return ''
|
||||
|
||||
def get_version():
|
||||
""" Returns version from moto/__init__.py
|
||||
"""
|
||||
version_file = read('moto', '__init__.py')
|
||||
version_match = re.search(r'^__version__ = [\'"]([^\'"]*)[\'"]',
|
||||
version_file, re.MULTILINE)
|
||||
if version_match:
|
||||
return version_match.group(1)
|
||||
raise RuntimeError('Unable to find version string.')
|
||||
|
||||
|
||||
def release_version_correct():
|
||||
"""Makes sure the:
|
||||
- prerelease verion for master is correct.
|
||||
- release version is correct for tags.
|
||||
"""
|
||||
if is_master_branch():
|
||||
# update for a pre release version.
|
||||
initpy = os.path.abspath("moto/__init__.py")
|
||||
|
||||
new_version = prerelease_version()
|
||||
print('updating version in __init__.py to {new_version}'.format(new_version=new_version))
|
||||
migrate_version(initpy, new_version)
|
||||
else:
|
||||
# check that we are a tag with the same version as in __init__.py
|
||||
assert get_version() == git_tag_name(), 'git tag/branch name not the same as moto/__init__.py __verion__'
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
release_version_correct()
|
Loading…
x
Reference in New Issue
Block a user