Introduce Github Actions to replace TravisCI (#3610)
This commit is contained in:
parent
5a41866f71
commit
8591eda9d6
@ -9,3 +9,4 @@ exclude_lines =
|
|||||||
[run]
|
[run]
|
||||||
include = moto/*
|
include = moto/*
|
||||||
omit = moto/packages/*
|
omit = moto/packages/*
|
||||||
|
source = moto
|
||||||
|
192
.github/workflows/build.yml
vendored
192
.github/workflows/build.yml
vendored
@ -1,7 +1,191 @@
|
|||||||
name: Example Action
|
name: TestNDeploy
|
||||||
on: [push]
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
pull_request:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
job1:
|
# Install and cache dependencies
|
||||||
|
cache:
|
||||||
|
name: Caching
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
python-version: [ 2.7, 3.6, 3.7, 3.8 ]
|
||||||
|
|
||||||
steps:
|
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 }}
|
||||||
|
|
||||||
|
68
.travis.yml
68
.travis.yml
@ -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
|
|
7
Makefile
7
Makefile
@ -26,7 +26,12 @@ format:
|
|||||||
test-only:
|
test-only:
|
||||||
rm -f .coverage
|
rm -f .coverage
|
||||||
rm -rf cover
|
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
|
test: lint test-only
|
||||||
|
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
[](https://gitter.im/awsmoto/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
[](https://gitter.im/awsmoto/Lobby?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||||
|
|
||||||
[](https://travis-ci.org/spulec/moto)
|
[](https://github.com/spulec/moto/actions)
|
||||||
[](https://coveralls.io/r/spulec/moto)
|
[](https://coveralls.io/r/spulec/moto)
|
||||||
[](http://docs.getmoto.org)
|
[](http://docs.getmoto.org)
|
||||||

|

|
||||||
@ -441,8 +441,8 @@ As a result, you need to add that entry to your host file for your tests to func
|
|||||||
|
|
||||||
## Releases
|
## Releases
|
||||||
|
|
||||||
Releases are done from travisci. Fairly closely following this:
|
Releases are done from Gitlab Actions. Fairly closely following this:
|
||||||
https://docs.travis-ci.com/user/deployment/pypi/
|
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 `master` branch do a dev deploy to pypi.
|
||||||
- Commits to a tag do a real deploy to pypi.
|
- Commits to a tag do a real deploy to pypi.
|
||||||
|
@ -4,3 +4,6 @@ universal=1
|
|||||||
[tool:pytest]
|
[tool:pytest]
|
||||||
markers =
|
markers =
|
||||||
network: marks tests which require network connection
|
network: marks tests which require network connection
|
||||||
|
|
||||||
|
[coverage:run]
|
||||||
|
relative_files = True
|
||||||
|
@ -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
|
@mock_iam
|
||||||
def test_create_open_id_connect_provider_errors():
|
def test_create_open_id_connect_provider_errors():
|
||||||
client = boto3.client("iam", region_name="us-east-1")
|
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=[]
|
Url="https://example.com", ThumbprintList=[]
|
||||||
).should.throw(ClientError, "Unknown")
|
).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(
|
@mock_iam
|
||||||
Url="example", ThumbprintList=[]
|
def test_create_open_id_connect_provider_too_many_entries():
|
||||||
).should.throw(ClientError, "Invalid Open ID Connect Provider URL")
|
client = boto3.client("iam", region_name="us-east-1")
|
||||||
|
|
||||||
client.create_open_id_connect_provider.when.called_with(
|
with pytest.raises(ClientError) as e:
|
||||||
Url="http://example.org",
|
client.create_open_id_connect_provider(
|
||||||
ThumbprintList=["a" * 40, "b" * 40, "c" * 40, "d" * 40, "e" * 40, "f" * 40,],
|
Url="http://example.org",
|
||||||
).should.throw(ClientError, "Thumbprint list must contain fewer than 5 entries.")
|
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)]
|
too_many_client_ids = ["{}".format(i) for i in range(101)]
|
||||||
client.create_open_id_connect_provider.when.called_with(
|
with pytest.raises(ClientError) as e:
|
||||||
Url="http://example.org", ThumbprintList=[], ClientIDList=too_many_client_ids,
|
client.create_open_id_connect_provider(
|
||||||
).should.throw(
|
Url="http://example.org",
|
||||||
ClientError, "Cannot exceed quota for ClientIdsPerOpenIdConnectProvider: 100",
|
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_url = "b" * 256
|
||||||
too_long_thumbprint = "b" * 41
|
too_long_thumbprint = "b" * 41
|
||||||
too_long_client_id = "b" * 256
|
too_long_client_id = "b" * 256
|
||||||
client.create_open_id_connect_provider.when.called_with(
|
with pytest.raises(ClientError) as e:
|
||||||
Url=too_long_url,
|
client.create_open_id_connect_provider(
|
||||||
ThumbprintList=[too_long_thumbprint],
|
Url=too_long_url,
|
||||||
ClientIDList=[too_long_client_id],
|
ThumbprintList=[too_long_thumbprint],
|
||||||
).should.throw(
|
ClientIDList=[too_long_client_id],
|
||||||
ClientError,
|
)
|
||||||
"3 validation errors detected: "
|
msg = e.value.response["Error"]["Message"]
|
||||||
'Value "{0}" at "clientIDList" failed to satisfy constraint: '
|
msg.should.contain("3 validation errors detected:")
|
||||||
"Member must satisfy constraint: "
|
msg.should.contain('"clientIDList" failed to satisfy constraint:')
|
||||||
"[Member must have length less than or equal to 255, "
|
msg.should.contain("Member must have length less than or equal to 255")
|
||||||
"Member must have length greater than or equal to 1]; "
|
msg.should.contain("Member must have length greater than or equal to 1")
|
||||||
'Value "{1}" at "thumbprintList" failed to satisfy constraint: '
|
msg.should.contain('"thumbprintList" failed to satisfy constraint:')
|
||||||
"Member must satisfy constraint: "
|
msg.should.contain("Member must have length less than or equal to 40")
|
||||||
"[Member must have length less than or equal to 40, "
|
msg.should.contain("Member must have length greater than or equal to 40")
|
||||||
"Member must have length greater than or equal to 40]; "
|
msg.should.contain('"url" failed to satisfy constraint:')
|
||||||
'Value "{2}" at "url" failed to satisfy constraint: '
|
msg.should.contain("Member must have length less than or equal to 255")
|
||||||
"Member must have length less than or equal to 255".format(
|
|
||||||
[too_long_client_id], [too_long_thumbprint], too_long_url
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@mock_iam
|
@mock_iam
|
||||||
|
@ -831,9 +831,8 @@ def test_describe_parameters_with_parameter_filters_path():
|
|||||||
|
|
||||||
|
|
||||||
@mock_ssm
|
@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 = boto3.client("ssm", region_name="us-east-1")
|
||||||
|
|
||||||
client.describe_parameters.when.called_with(
|
client.describe_parameters.when.called_with(
|
||||||
Filters=[{"Key": "Name", "Values": ["test"]}],
|
Filters=[{"Key": "Name", "Values": ["test"]}],
|
||||||
ParameterFilters=[{"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.",
|
"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(
|
@pytest.mark.parametrize(
|
||||||
ParameterFilters=[{"Key": "key"}]
|
"filters,error_msg",
|
||||||
).should.throw(
|
[
|
||||||
ClientError,
|
(
|
||||||
'1 validation error detected: Value "key" at "parameterFilters.1.member.key" failed to satisfy constraint: '
|
[{"Key": "key"}],
|
||||||
"Member must satisfy regular expression pattern: tag:.+|Name|Type|KeyId|Path|Label|Tier",
|
"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
|
|
||||||
),
|
),
|
||||||
)
|
(
|
||||||
|
[{"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(
|
with pytest.raises(ClientError) as e:
|
||||||
ParameterFilters=[{"Key": "Name", "Option": "over 10 chars"}, {"Key": "key"}]
|
client.describe_parameters(ParameterFilters=filters)
|
||||||
).should.throw(
|
e.value.response["Error"]["Message"].should.contain(error_msg)
|
||||||
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",
|
|
||||||
)
|
|
||||||
|
|
||||||
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(
|
@pytest.mark.parametrize("value", ["/###", "//", "test"])
|
||||||
ParameterFilters=[{"Key": "Name"}]
|
@mock_ssm
|
||||||
).should.throw(
|
def test_describe_parameters_invalid_path(value):
|
||||||
ClientError,
|
client = boto3.client("ssm", region_name="us-east-1")
|
||||||
"The following filter values are missing : null for filter key Name.",
|
|
||||||
)
|
|
||||||
|
|
||||||
client.describe_parameters.when.called_with(
|
with pytest.raises(ClientError) as e:
|
||||||
ParameterFilters=[{"Key": "Name", "Values": []}]
|
client.describe_parameters(
|
||||||
).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(
|
|
||||||
ParameterFilters=[{"Key": "Path", "Values": [value]}]
|
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_.",
|
|
||||||
)
|
)
|
||||||
|
msg = e.value.response["Error"]["Message"]
|
||||||
client.describe_parameters.when.called_with(
|
msg.should.contain("The parameter doesn't meet the parameter name requirements")
|
||||||
ParameterFilters=[{"Key": "Path", "Values": ["/aws", "/ssm"]}]
|
msg.should.contain('The parameter name must begin with a forward slash "/".')
|
||||||
).should.throw(
|
msg.should.contain('It can\'t be prefixed with "aws" or "ssm" (case-insensitive).')
|
||||||
ClientError,
|
msg.should.contain(
|
||||||
'Filters for common parameters can\'t be prefixed with "aws" or "ssm" (case-insensitive). '
|
"It must use only letters, numbers, or the following symbols: . (period), - (hyphen), _ (underscore)."
|
||||||
"When using global parameters, please specify within a global namespace.",
|
|
||||||
)
|
)
|
||||||
|
msg.should.contain(
|
||||||
client.describe_parameters.when.called_with(
|
'Special characters are not allowed. All sub-paths, if specified, must use the forward slash symbol "/".'
|
||||||
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("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(
|
@pytest.mark.parametrize(
|
||||||
ParameterFilters=[{"Key": "Type", "Values": ["test"]}]
|
"filters,error_msg",
|
||||||
).should.throw(
|
[
|
||||||
ClientError,
|
([{}], 'Missing required parameter in ParameterFilters[0]: "Key"',),
|
||||||
"The following filter value is not valid: test. Valid values include: [String, StringList, SecureString]",
|
(
|
||||||
)
|
[{"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(
|
with pytest.raises(ParamValidationError) as e:
|
||||||
ParameterFilters=[{"Key": "Name", "Option": "option", "Values": ["test"]}]
|
client.describe_parameters(ParameterFilters=filters)
|
||||||
).should.throw(
|
e.value.kwargs["report"].should.contain(error_msg)
|
||||||
ClientError,
|
|
||||||
"The following filter option is not valid: option. Valid options include: [BeginsWith, Equals].",
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@mock_ssm
|
@mock_ssm
|
||||||
|
@ -25,7 +25,7 @@ while True:
|
|||||||
break
|
break
|
||||||
except EXCEPTIONS:
|
except EXCEPTIONS:
|
||||||
elapsed_s = time.time() - start_ts
|
elapsed_s = time.time() - start_ts
|
||||||
if elapsed_s > 60:
|
if elapsed_s > 120:
|
||||||
raise
|
raise
|
||||||
|
|
||||||
print(".")
|
print(".")
|
||||||
|
Loading…
Reference in New Issue
Block a user