From 8591eda9d62ad66a11e8971d513a8b35c4b469d3 Mon Sep 17 00:00:00 2001 From: Bert Blommers Date: Tue, 26 Jan 2021 12:37:03 +0000 Subject: [PATCH] Introduce Github Actions to replace TravisCI (#3610) --- .coveragerc | 1 + .github/workflows/build.yml | 192 +++++++++++++++++++++++++- .travis.yml | 68 --------- Makefile | 7 +- README.md | 6 +- setup.cfg | 3 + tests/test_iam/test_iam.py | 96 ++++++++----- tests/test_ssm/test_ssm_boto3.py | 229 ++++++++++++++----------------- wait_for.py | 2 +- 9 files changed, 364 insertions(+), 240 deletions(-) delete mode 100644 .travis.yml diff --git a/.coveragerc b/.coveragerc index 2130ec2ad..2258101db 100644 --- a/.coveragerc +++ b/.coveragerc @@ -9,3 +9,4 @@ exclude_lines = [run] include = moto/* omit = moto/packages/* +source = moto diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ce988332e..452669b5d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -1,7 +1,191 @@ -name: Example Action -on: [push] +name: TestNDeploy + +on: + push: + branches: + - master + pull_request: + jobs: - job1: + # Install and cache dependencies + cache: + name: Caching runs-on: ubuntu-latest + strategy: + matrix: + python-version: [ 2.7, 3.6, 3.7, 3.8 ] + steps: - - run: echo "Test" + - uses: actions/checkout@v2 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + - name: Get pip cache dir + id: pip-cache-dir + run: | + echo "::set-output name=dir::$(pip cache dir)" + - name: pip cache + id: pip-cache + uses: actions/cache@v2 + with: + path: ${{ steps.pip-cache-dir.outputs.dir }} + key: pip-${{ matrix.python-version }}-${{ hashFiles('**/requirements-dev.txt') }} + - name: Install Linux dependencies + if: ${{ matrix.python-version == 3.8 && steps.pip-cache.outputs.cache-hit != 'true' }} + run: | + sudo apt-get install libxslt-dev libxml2-dev -y + - name: Install project dependencies + if: ${{ steps.pip-cache.outputs.cache-hit != 'true' }} + run: | + python -m pip install --upgrade pip + pip install -r requirements-dev.txt + + lint: + name: Linting + runs-on: ubuntu-latest + needs: cache + strategy: + matrix: + python-version: [3.7] + steps: + - uses: actions/checkout@v2 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + # Retrieve the previously cached dependencies + - name: Get pip cache dir + id: pip-cache + run: | + echo "::set-output name=dir::$(pip cache dir)" + - name: pip cache + uses: actions/cache@v2 + with: + path: ${{ steps.pip-cache.outputs.dir }} + key: pip-${{ matrix.python-version }}-${{ hashFiles('**/requirements-dev.txt') }} + # Still need to properly install the dependencies - it will only skip the download part + - name: Install project dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements-dev.txt + - name: Lint with flake8 + run: + make lint + + test: + name: Unit test + runs-on: ubuntu-latest + needs: lint + strategy: + fail-fast: false + matrix: + python-version: [2.7, 3.6, 3.7, 3.8] + + steps: + - uses: actions/checkout@v2 + with: + fetch-depth: 0 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + - name: Get pip cache dir + id: pip-cache + run: | + echo "::set-output name=dir::$(pip cache dir)" + - name: pip cache + uses: actions/cache@v2 + with: + path: ${{ steps.pip-cache.outputs.dir }} + key: pip-${{ matrix.python-version }}-${{ hashFiles('**/requirements-dev.txt') }} + - name: Install Linux dependencies + if: ${{ matrix.python-version == 3.8 }} + run: | + sudo apt-get install libxslt-dev libxml2-dev -y + - name: Install project dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements-dev.txt + pip install pytest-cov + - name: Test with pytest + if: ${{ matrix.python-version != 3.7 }} + run: | + make test-only + # Only need to configure coverage once + # Pytest-cov explicitly fails in Py2 for XRay tests + - name: Test with pytest/coverage + if: ${{ matrix.python-version == 3.7 }} + run: | + make test-coverage + - name: "Upload coverage to Codecov" + if: ${{ matrix.python-version == 3.7 }} + uses: codecov/codecov-action@v1 + with: + fail_ci_if_error: true + + testserver: + name: Unit tests in Server Mode + runs-on: ubuntu-latest + needs: lint + strategy: + matrix: + python-version: [2.7, 3.6, 3.7, 3.8] + + steps: + - uses: actions/checkout@v2 + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + - name: Start MotoServer + run: | + python setup.py sdist + 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:3.7-buster /moto/travis_moto_server.sh & + python wait_for.py + - name: Get pip cache dir + id: pip-cache + run: | + echo "::set-output name=dir::$(pip cache dir)" + - name: pip cache + uses: actions/cache@v2 + with: + path: ${{ steps.pip-cache.outputs.dir }} + key: pip-${{ matrix.python-version }}-${{ hashFiles('**/requirements-dev.txt') }} + - name: Install Linux dependencies + if: ${{ matrix.python-version == 3.8 }} + run: | + sudo apt-get install libxslt-dev libxml2-dev -y + - name: Install project dependencies + run: | + python -m pip install --upgrade pip + pip install -r requirements-dev.txt + - name: Test ServerMode + env: + TEST_SERVER_MODE: ${{ true }} + run: | + make test-only + + deploy: + name: Deploy + runs-on: ubuntu-latest + needs: [test, testserver] + if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }} + strategy: + matrix: + python-version: [3.8] + steps: + - uses: actions/checkout@v2 + - name: Update project version + run: | + git fetch --unshallow + python update_version_from_git.py + - name: Build project + run: | + pip install wheel + python setup.py sdist bdist_wheel + - name: Publish to PyPI + uses: pypa/gh-action-pypi-publish@master + with: + password: ${{ secrets.PYPI_API_TOKEN }} + diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 824eb0edc..000000000 --- a/.travis.yml +++ /dev/null @@ -1,68 +0,0 @@ -dist: focal -language: python -services: -- docker -python: -- 2.7 -- 3.6 -- 3.7 -- 3.8 -env: -- TEST_SERVER_MODE=false -- TEST_SERVER_MODE=true -before_install: -- export BOTO_CONFIG=/dev/null -install: -- | - python setup.py sdist - - if [ "$TEST_SERVER_MODE" = "true" ]; then - if [ "$TRAVIS_PYTHON_VERSION" = "3.8" ]; then - # Python 3.8 does not provide Stretch images yet [1] - # [1] https://github.com/docker-library/python/issues/428 - PYTHON_DOCKER_TAG=${TRAVIS_PYTHON_VERSION}-buster - else - PYTHON_DOCKER_TAG=${TRAVIS_PYTHON_VERSION}-stretch - fi - docker run --rm -t --name motoserver -e TEST_SERVER_MODE=true -e AWS_SECRET_ACCESS_KEY=server_secret -e AWS_ACCESS_KEY_ID=server_key -v `pwd`:/moto -p 5000:5000 -v /var/run/docker.sock:/var/run/docker.sock python:${PYTHON_DOCKER_TAG} /moto/travis_moto_server.sh & - fi - travis_retry pip install -r requirements-dev.txt - travis_retry pip install docker>=2.5.1 - 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 coverage==4.5.4 - - if [ "$TEST_SERVER_MODE" = "true" ]; then - python wait_for.py - fi -before_script: -- if [[ $TRAVIS_PYTHON_VERSION == "3.7" ]]; then make lint; fi -script: -- make test-only -after_success: -- 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 diff --git a/Makefile b/Makefile index b155b6f8e..37ab168fe 100644 --- a/Makefile +++ b/Makefile @@ -26,7 +26,12 @@ format: test-only: rm -f .coverage rm -rf cover - @pytest -sv --cov=moto --cov-report html ./tests/ $(TEST_EXCLUDE) + pytest -sv ./tests/ $(TEST_EXCLUDE) + +test-coverage: + rm -f .coverage + rm -rf cover + pytest -sv --cov=moto --cov-report xml ./tests/ $(TEST_EXCLUDE) test: lint test-only diff --git a/README.md b/README.md index 1eccf7a5d..ba69988b5 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ [![Join the chat at https://gitter.im/awsmoto/Lobby](https://badges.gitter.im/awsmoto/Lobby.svg)](https://gitter.im/awsmoto/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) -[![Build Status](https://travis-ci.org/spulec/moto.svg?branch=master)](https://travis-ci.org/spulec/moto) +[![Build Status](https://github.com/spulec/moto/workflows/TestNDeploy/badge.svg)](https://github.com/spulec/moto/actions) [![Coverage Status](https://coveralls.io/repos/spulec/moto/badge.svg?branch=master)](https://coveralls.io/r/spulec/moto) [![Docs](https://readthedocs.org/projects/pip/badge/?version=stable)](http://docs.getmoto.org) ![PyPI](https://img.shields.io/pypi/v/moto.svg) @@ -441,8 +441,8 @@ As a result, you need to add that entry to your host file for your tests to func ## Releases -Releases are done from travisci. Fairly closely following this: -https://docs.travis-ci.com/user/deployment/pypi/ +Releases are done from Gitlab Actions. Fairly closely following this: +https://packaging.python.org/guides/publishing-package-distribution-releases-using-github-actions-ci-cd-workflows/ - Commits to `master` branch do a dev deploy to pypi. - Commits to a tag do a real deploy to pypi. diff --git a/setup.cfg b/setup.cfg index 1c247ef3d..b7aa23edf 100644 --- a/setup.cfg +++ b/setup.cfg @@ -4,3 +4,6 @@ universal=1 [tool:pytest] markers = network: marks tests which require network connection + +[coverage:run] +relative_files = True diff --git a/tests/test_iam/test_iam.py b/tests/test_iam/test_iam.py index 038169316..ae30b77fb 100644 --- a/tests/test_iam/test_iam.py +++ b/tests/test_iam/test_iam.py @@ -2523,6 +2523,16 @@ def test_create_open_id_connect_provider(): ) +@pytest.mark.parametrize("url", ["example.org", "example"]) +@mock_iam +def test_create_open_id_connect_provider_invalid_url(url): + client = boto3.client("iam", region_name="us-east-1") + with pytest.raises(ClientError) as e: + client.create_open_id_connect_provider(Url=url, ThumbprintList=[]) + msg = e.value.response["Error"]["Message"] + msg.should.contain("Invalid Open ID Connect Provider URL") + + @mock_iam def test_create_open_id_connect_provider_errors(): client = boto3.client("iam", region_name="us-east-1") @@ -2532,49 +2542,65 @@ def test_create_open_id_connect_provider_errors(): Url="https://example.com", ThumbprintList=[] ).should.throw(ClientError, "Unknown") - client.create_open_id_connect_provider.when.called_with( - Url="example.org", ThumbprintList=[] - ).should.throw(ClientError, "Invalid Open ID Connect Provider URL") - client.create_open_id_connect_provider.when.called_with( - Url="example", ThumbprintList=[] - ).should.throw(ClientError, "Invalid Open ID Connect Provider URL") +@mock_iam +def test_create_open_id_connect_provider_too_many_entries(): + client = boto3.client("iam", region_name="us-east-1") - client.create_open_id_connect_provider.when.called_with( - Url="http://example.org", - ThumbprintList=["a" * 40, "b" * 40, "c" * 40, "d" * 40, "e" * 40, "f" * 40,], - ).should.throw(ClientError, "Thumbprint list must contain fewer than 5 entries.") + with pytest.raises(ClientError) as e: + client.create_open_id_connect_provider( + Url="http://example.org", + ThumbprintList=[ + "a" * 40, + "b" * 40, + "c" * 40, + "d" * 40, + "e" * 40, + "f" * 40, + ], + ) + msg = e.value.response["Error"]["Message"] + msg.should.contain("Thumbprint list must contain fewer than 5 entries.") + + +@mock_iam +def test_create_open_id_connect_provider_quota_error(): + client = boto3.client("iam", region_name="us-east-1") too_many_client_ids = ["{}".format(i) for i in range(101)] - client.create_open_id_connect_provider.when.called_with( - Url="http://example.org", ThumbprintList=[], ClientIDList=too_many_client_ids, - ).should.throw( - ClientError, "Cannot exceed quota for ClientIdsPerOpenIdConnectProvider: 100", - ) + with pytest.raises(ClientError) as e: + client.create_open_id_connect_provider( + Url="http://example.org", + ThumbprintList=[], + ClientIDList=too_many_client_ids, + ) + msg = e.value.response["Error"]["Message"] + msg.should.contain("Cannot exceed quota for ClientIdsPerOpenIdConnectProvider: 100") + + +@mock_iam +def test_create_open_id_connect_provider_multiple_errors(): + client = boto3.client("iam", region_name="us-east-1") too_long_url = "b" * 256 too_long_thumbprint = "b" * 41 too_long_client_id = "b" * 256 - client.create_open_id_connect_provider.when.called_with( - Url=too_long_url, - ThumbprintList=[too_long_thumbprint], - ClientIDList=[too_long_client_id], - ).should.throw( - ClientError, - "3 validation errors detected: " - 'Value "{0}" at "clientIDList" failed to satisfy constraint: ' - "Member must satisfy constraint: " - "[Member must have length less than or equal to 255, " - "Member must have length greater than or equal to 1]; " - 'Value "{1}" at "thumbprintList" failed to satisfy constraint: ' - "Member must satisfy constraint: " - "[Member must have length less than or equal to 40, " - "Member must have length greater than or equal to 40]; " - 'Value "{2}" at "url" failed to satisfy constraint: ' - "Member must have length less than or equal to 255".format( - [too_long_client_id], [too_long_thumbprint], too_long_url - ), - ) + with pytest.raises(ClientError) as e: + client.create_open_id_connect_provider( + Url=too_long_url, + ThumbprintList=[too_long_thumbprint], + ClientIDList=[too_long_client_id], + ) + msg = e.value.response["Error"]["Message"] + msg.should.contain("3 validation errors detected:") + msg.should.contain('"clientIDList" failed to satisfy constraint:') + msg.should.contain("Member must have length less than or equal to 255") + msg.should.contain("Member must have length greater than or equal to 1") + msg.should.contain('"thumbprintList" failed to satisfy constraint:') + msg.should.contain("Member must have length less than or equal to 40") + msg.should.contain("Member must have length greater than or equal to 40") + msg.should.contain('"url" failed to satisfy constraint:') + msg.should.contain("Member must have length less than or equal to 255") @mock_iam diff --git a/tests/test_ssm/test_ssm_boto3.py b/tests/test_ssm/test_ssm_boto3.py index 5aad14429..8fd12a451 100644 --- a/tests/test_ssm/test_ssm_boto3.py +++ b/tests/test_ssm/test_ssm_boto3.py @@ -831,9 +831,8 @@ def test_describe_parameters_with_parameter_filters_path(): @mock_ssm -def test_describe_parameters_invalid_parameter_filters(): +def test_describe_parameters_needs_param(): client = boto3.client("ssm", region_name="us-east-1") - client.describe_parameters.when.called_with( Filters=[{"Key": "Name", "Values": ["test"]}], ParameterFilters=[{"Key": "Name", "Values": ["test"]}], @@ -842,145 +841,119 @@ def test_describe_parameters_invalid_parameter_filters(): "You can use either Filters or ParameterFilters in a single request.", ) - client.describe_parameters.when.called_with(ParameterFilters=[{}]).should.throw( - ParamValidationError, - 'Parameter validation failed:\nMissing required parameter in ParameterFilters[0]: "Key"', - ) - client.describe_parameters.when.called_with( - ParameterFilters=[{"Key": "key"}] - ).should.throw( - ClientError, - '1 validation error detected: Value "key" at "parameterFilters.1.member.key" failed to satisfy constraint: ' - "Member must satisfy regular expression pattern: tag:.+|Name|Type|KeyId|Path|Label|Tier", - ) - - long_key = "tag:" + "t" * 129 - client.describe_parameters.when.called_with( - ParameterFilters=[{"Key": long_key}] - ).should.throw( - ClientError, - '1 validation error detected: Value "{value}" at "parameterFilters.1.member.key" failed to satisfy constraint: ' - "Member must have length less than or equal to 132".format(value=long_key), - ) - - client.describe_parameters.when.called_with( - ParameterFilters=[{"Key": "Name", "Option": "over 10 chars"}] - ).should.throw( - ClientError, - '1 validation error detected: Value "over 10 chars" at "parameterFilters.1.member.option" failed to satisfy constraint: ' - "Member must have length less than or equal to 10", - ) - - many_values = ["test"] * 51 - client.describe_parameters.when.called_with( - ParameterFilters=[{"Key": "Name", "Values": many_values}] - ).should.throw( - ClientError, - '1 validation error detected: Value "{value}" at "parameterFilters.1.member.values" failed to satisfy constraint: ' - "Member must have length less than or equal to 50".format(value=many_values), - ) - - long_value = ["t" * 1025] - client.describe_parameters.when.called_with( - ParameterFilters=[{"Key": "Name", "Values": long_value}] - ).should.throw( - ClientError, - '1 validation error detected: Value "{value}" at "parameterFilters.1.member.values" failed to satisfy constraint: ' - "[Member must have length less than or equal to 1024, Member must have length greater than or equal to 1]".format( - value=long_value +@pytest.mark.parametrize( + "filters,error_msg", + [ + ( + [{"Key": "key"}], + "Member must satisfy regular expression pattern: tag:.+|Name|Type|KeyId|Path|Label|Tier", ), - ) + ( + [{"Key": "tag:" + "t" * 129}], + "Member must have length less than or equal to 132", + ), + ( + [{"Key": "Name", "Option": "over 10 chars"}], + "Member must have length less than or equal to 10", + ), + ( + [{"Key": "Name", "Values": ["test"] * 51}], + "Member must have length less than or equal to 50", + ), + ( + [{"Key": "Name", "Values": ["t" * 1025]}], + "Member must have length less than or equal to 1024, Member must have length greater than or equal to 1", + ), + ( + [{"Key": "Name", "Option": "over 10 chars"}, {"Key": "key"}], + "2 validation errors detected:", + ), + ( + [{"Key": "Label"}], + "The following filter key is not valid: Label. Valid filter keys include: [Path, Name, Type, KeyId, Tier]", + ), + ( + [{"Key": "Name"}], + "The following filter values are missing : null for filter key Name", + ), + ( + [ + {"Key": "Name", "Values": ["test"]}, + {"Key": "Name", "Values": ["test test"]}, + ], + "The following filter is duplicated in the request: Name. A request can contain only one occurrence of a specific filter.", + ), + ( + [{"Key": "Path", "Values": ["/aws", "/ssm"]}], + 'Filters for common parameters can\'t be prefixed with "aws" or "ssm" (case-insensitive).', + ), + ( + [{"Key": "Path", "Option": "Equals", "Values": ["test"]}], + "The following filter option is not valid: Equals. Valid options include: [Recursive, OneLevel]", + ), + ( + [{"Key": "Tier", "Values": ["test"]}], + "The following filter value is not valid: test. Valid values include: [Standard, Advanced, Intelligent-Tiering]", + ), + ( + [{"Key": "Type", "Values": ["test"]}], + "The following filter value is not valid: test. Valid values include: [String, StringList, SecureString]", + ), + ( + [{"Key": "Name", "Option": "option", "Values": ["test"]}], + "The following filter option is not valid: option. Valid options include: [BeginsWith, Equals].", + ), + ], +) +@mock_ssm +def test_describe_parameters_invalid_parameter_filters(filters, error_msg): + client = boto3.client("ssm", region_name="us-east-1") - client.describe_parameters.when.called_with( - ParameterFilters=[{"Key": "Name", "Option": "over 10 chars"}, {"Key": "key"}] - ).should.throw( - ClientError, - "2 validation errors detected: " - 'Value "over 10 chars" at "parameterFilters.1.member.option" failed to satisfy constraint: ' - "Member must have length less than or equal to 10; " - 'Value "key" at "parameterFilters.2.member.key" failed to satisfy constraint: ' - "Member must satisfy regular expression pattern: tag:.+|Name|Type|KeyId|Path|Label|Tier", - ) + with pytest.raises(ClientError) as e: + client.describe_parameters(ParameterFilters=filters) + e.value.response["Error"]["Message"].should.contain(error_msg) - client.describe_parameters.when.called_with( - ParameterFilters=[{"Key": "Label"}] - ).should.throw( - ClientError, - "The following filter key is not valid: Label. Valid filter keys include: [Path, Name, Type, KeyId, Tier].", - ) - client.describe_parameters.when.called_with( - ParameterFilters=[{"Key": "Name"}] - ).should.throw( - ClientError, - "The following filter values are missing : null for filter key Name.", - ) +@pytest.mark.parametrize("value", ["/###", "//", "test"]) +@mock_ssm +def test_describe_parameters_invalid_path(value): + client = boto3.client("ssm", region_name="us-east-1") - client.describe_parameters.when.called_with( - ParameterFilters=[{"Key": "Name", "Values": []}] - ).should.throw( - ParamValidationError, - "Invalid length for parameter ParameterFilters[0].Values, value: 0, valid range: 1-inf", - ) - - client.describe_parameters.when.called_with( - ParameterFilters=[ - {"Key": "Name", "Values": ["test"]}, - {"Key": "Name", "Values": ["test test"]}, - ] - ).should.throw( - ClientError, - "The following filter is duplicated in the request: Name. A request can contain only one occurrence of a specific filter.", - ) - - for value in ["/###", "//", "test"]: - client.describe_parameters.when.called_with( + with pytest.raises(ClientError) as e: + client.describe_parameters( ParameterFilters=[{"Key": "Path", "Values": [value]}] - ).should.throw( - ClientError, - 'The parameter doesn\'t meet the parameter name requirements. The parameter name must begin with a forward slash "/". ' - 'It can\'t be prefixed with "aws" or "ssm" (case-insensitive). ' - "It must use only letters, numbers, or the following symbols: . (period), - (hyphen), _ (underscore). " - 'Special characters are not allowed. All sub-paths, if specified, must use the forward slash symbol "/". ' - "Valid example: /get/parameters2-/by1./path0_.", ) - - client.describe_parameters.when.called_with( - ParameterFilters=[{"Key": "Path", "Values": ["/aws", "/ssm"]}] - ).should.throw( - ClientError, - 'Filters for common parameters can\'t be prefixed with "aws" or "ssm" (case-insensitive). ' - "When using global parameters, please specify within a global namespace.", + msg = e.value.response["Error"]["Message"] + msg.should.contain("The parameter doesn't meet the parameter name requirements") + msg.should.contain('The parameter name must begin with a forward slash "/".') + msg.should.contain('It can\'t be prefixed with "aws" or "ssm" (case-insensitive).') + msg.should.contain( + "It must use only letters, numbers, or the following symbols: . (period), - (hyphen), _ (underscore)." ) - - client.describe_parameters.when.called_with( - ParameterFilters=[{"Key": "Path", "Option": "Equals", "Values": ["test"]}] - ).should.throw( - ClientError, - "The following filter option is not valid: Equals. Valid options include: [Recursive, OneLevel].", + msg.should.contain( + 'Special characters are not allowed. All sub-paths, if specified, must use the forward slash symbol "/".' ) + msg.should.contain("Valid example: /get/parameters2-/by1./path0_.") - client.describe_parameters.when.called_with( - ParameterFilters=[{"Key": "Tier", "Values": ["test"]}] - ).should.throw( - ClientError, - "The following filter value is not valid: test. Valid values include: [Standard, Advanced, Intelligent-Tiering]", - ) - client.describe_parameters.when.called_with( - ParameterFilters=[{"Key": "Type", "Values": ["test"]}] - ).should.throw( - ClientError, - "The following filter value is not valid: test. Valid values include: [String, StringList, SecureString]", - ) +@pytest.mark.parametrize( + "filters,error_msg", + [ + ([{}], 'Missing required parameter in ParameterFilters[0]: "Key"',), + ( + [{"Key": "Name", "Values": []}], + "Invalid length for parameter ParameterFilters[0].Values, value: 0, valid range: 1-inf", + ), + ], +) +@mock_ssm +def test_describe_parameters_parameter_validation(filters, error_msg): + client = boto3.client("ssm", region_name="us-east-1") - client.describe_parameters.when.called_with( - ParameterFilters=[{"Key": "Name", "Option": "option", "Values": ["test"]}] - ).should.throw( - ClientError, - "The following filter option is not valid: option. Valid options include: [BeginsWith, Equals].", - ) + with pytest.raises(ParamValidationError) as e: + client.describe_parameters(ParameterFilters=filters) + e.value.kwargs["report"].should.contain(error_msg) @mock_ssm diff --git a/wait_for.py b/wait_for.py index be29b0140..919a5cb73 100755 --- a/wait_for.py +++ b/wait_for.py @@ -25,7 +25,7 @@ while True: break except EXCEPTIONS: elapsed_s = time.time() - start_ts - if elapsed_s > 60: + if elapsed_s > 120: raise print(".")