Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add new "require_boto(3|core)_at_least() helpers #446

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions plugins/module_utils/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,25 @@ def _gather_versions(self):
return dict(boto3_version=boto3.__version__,
botocore_version=botocore.__version__)

def require_boto3_at_least(self, desired, **kwargs):
"""Check if the available boto3 version is greater than or equal to a desired version.

calls fail_json() when the boto3 version is less than the desired
version

Usage:
module.require_boto3_at_least("1.2.3", reason="to update tags")
module.require_boto3_at_least("1.1.1")

:param desired the minimum desired version
:param reason why the version is required (optional)
"""
if not self.boto3_at_least(desired):
self._module.fail_json(
msg=missing_required_lib('boto3>={0}'.format(desired), **kwargs),
**self._gather_versions()
)

def boto3_at_least(self, desired):
"""Check if the available boto3 version is greater than or equal to a desired version.

Expand All @@ -255,6 +274,25 @@ def boto3_at_least(self, desired):
existing = self._gather_versions()
return LooseVersion(existing['boto3_version']) >= LooseVersion(desired)

def require_botocore_at_least(self, desired, **kwargs):
"""Check if the available botocore version is greater than or equal to a desired version.

calls fail_json() when the botocore version is less than the desired
version

Usage:
module.require_botocore_at_least("1.2.3", reason="to update tags")
module.require_botocore_at_least("1.1.1")

:param desired the minimum desired version
:param reason why the version is required (optional)
"""
if not self.botocore_at_least(desired):
self._module.fail_json(
msg=missing_required_lib('botocore>={0}'.format(desired), **kwargs),
**self._gather_versions()
)

def botocore_at_least(self, desired):
"""Check if the available botocore version is greater than or equal to a desired version.

Expand Down
3 changes: 1 addition & 2 deletions plugins/modules/ec2_vol.py
Original file line number Diff line number Diff line change
Expand Up @@ -719,8 +719,7 @@ def main():
'Using the "list" state has been deprecated. Please use the ec2_vol_info module instead', date='2022-06-01', collection_name='amazon.aws')

if module.params.get('throughput'):
if not module.botocore_at_least("1.19.27"):
module.fail_json(msg="botocore >= 1.19.27 is required to set the throughput for a volume")
module.require_botocore_at_least('1.19.27', reason='to set the throughput for a volume')

# Ensure we have the zone or can get the zone
if instance is None and zone is None and state == 'present':
Expand Down
4 changes: 3 additions & 1 deletion tests/sanity/ignore-2.10.txt
Original file line number Diff line number Diff line change
Expand Up @@ -374,4 +374,6 @@ plugins/modules/s3_bucket.py import-3.5!skip
plugins/modules/s3_bucket.py import-3.6!skip
plugins/modules/s3_bucket.py import-3.7!skip
plugins/modules/s3_bucket.py metaclass-boilerplate!skip
tests/sanity/refresh_ignore_files shebang!skip
tests/sanity/refresh_ignore_files shebang!skip
tests/unit/module_utils/core/ansible_aws_module/test_require_at_least.py compile-2.6!skip
tests/unit/module_utils/core/ansible_aws_module/test_require_at_least.py compile-2.7!skip
4 changes: 3 additions & 1 deletion tests/sanity/ignore-2.11.txt
Original file line number Diff line number Diff line change
Expand Up @@ -374,4 +374,6 @@ plugins/modules/s3_bucket.py import-3.5!skip
plugins/modules/s3_bucket.py import-3.6!skip
plugins/modules/s3_bucket.py import-3.7!skip
plugins/modules/s3_bucket.py metaclass-boilerplate!skip
tests/sanity/refresh_ignore_files shebang!skip
tests/sanity/refresh_ignore_files shebang!skip
tests/unit/module_utils/core/ansible_aws_module/test_require_at_least.py compile-2.6!skip
tests/unit/module_utils/core/ansible_aws_module/test_require_at_least.py compile-2.7!skip
2 changes: 1 addition & 1 deletion tests/sanity/ignore-2.12.txt
Original file line number Diff line number Diff line change
Expand Up @@ -374,4 +374,4 @@ plugins/modules/s3_bucket.py import-3.5!skip
plugins/modules/s3_bucket.py import-3.6!skip
plugins/modules/s3_bucket.py import-3.7!skip
plugins/modules/s3_bucket.py metaclass-boilerplate!skip
tests/sanity/refresh_ignore_files shebang!skip
tests/sanity/refresh_ignore_files shebang!skip
2 changes: 2 additions & 0 deletions tests/sanity/ignore-2.9.txt
Original file line number Diff line number Diff line change
Expand Up @@ -392,3 +392,5 @@ plugins/modules/s3_bucket.py import-3.6!skip
plugins/modules/s3_bucket.py import-3.7!skip
plugins/modules/s3_bucket.py metaclass-boilerplate!skip
tests/sanity/refresh_ignore_files shebang!skip
tests/unit/module_utils/core/ansible_aws_module/test_require_at_least.py compile-2.6!skip
tests/unit/module_utils/core/ansible_aws_module/test_require_at_least.py compile-2.7!skip
Original file line number Diff line number Diff line change
@@ -0,0 +1,211 @@
# (c) 2021 Red Hat Inc.
#
# This file is part of Ansible
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)

from __future__ import (absolute_import, division, print_function)
__metaclass__ = type

import pytest
import botocore
import boto3
import json

from ansible_collections.amazon.aws.plugins.module_utils.core import AnsibleAWSModule

DUMMY_VERSION = '5.5.5.5'

TEST_VERSIONS = [
['1.1.1', '2.2.2', True],
['1.1.1', '0.0.1', False],
['9.9.9', '9.9.9', True],
['9.9.9', '9.9.10', True],
['9.9.9', '9.10.9', True],
['9.9.9', '10.9.9', True],
['9.9.9', '9.9.8', False],
['9.9.9', '9.8.9', False],
['9.9.9', '8.9.9', False],
['10.10.10', '10.10.10', True],
['10.10.10', '10.10.11', True],
['10.10.10', '10.11.10', True],
['10.10.10', '11.10.10', True],
['10.10.10', '10.10.9', False],
['10.10.10', '10.9.10', False],
['10.10.10', '9.19.10', False],
]


class TestRequireAtLeast(object):
# ========================================================
# Prepare some data for use in our testing
# ========================================================
def setup_method(self):
pass

# ========================================================
# Test botocore_at_least
# ========================================================
@pytest.mark.parametrize("stdin, desired_version, compare_version, at_least", [({}, *d) for d in TEST_VERSIONS], indirect=["stdin"])
def test_botocore_at_least(self, monkeypatch, stdin, desired_version, compare_version, at_least, capfd):
monkeypatch.setattr(botocore, "__version__", compare_version)
# Set boto3 version to a known value (tests are on both sides) to make
# sure we're comparing the right library
monkeypatch.setattr(boto3, "__version__", DUMMY_VERSION)

# Create a minimal module that we can call
module = AnsibleAWSModule(argument_spec=dict())

assert at_least == module.botocore_at_least(desired_version)

# ========================================================
# Test boto3_at_least
# ========================================================
@pytest.mark.parametrize("stdin, desired_version, compare_version, at_least", [({}, *d) for d in TEST_VERSIONS], indirect=["stdin"])
def test_boto3_at_least(self, monkeypatch, stdin, desired_version, compare_version, at_least, capfd):
# Set botocore version to a known value (tests are on both sides) to make
# sure we're comparing the right library
monkeypatch.setattr(botocore, "__version__", DUMMY_VERSION)
monkeypatch.setattr(boto3, "__version__", compare_version)

# Create a minimal module that we can call
module = AnsibleAWSModule(argument_spec=dict())

assert at_least == module.boto3_at_least(desired_version)

# ========================================================
# Test require_botocore_at_least
# ========================================================
@pytest.mark.parametrize("stdin, desired_version, compare_version, at_least", [({}, *d) for d in TEST_VERSIONS], indirect=["stdin"])
def test_require_botocore_at_least(self, monkeypatch, stdin, desired_version, compare_version, at_least, capfd):
monkeypatch.setattr(botocore, "__version__", compare_version)
# Set boto3 version to a known value (tests are on both sides) to make
# sure we're comparing the right library
monkeypatch.setattr(boto3, "__version__", DUMMY_VERSION)

# Create a minimal module that we can call
module = AnsibleAWSModule(argument_spec=dict())

with pytest.raises(SystemExit) as e:
module.require_botocore_at_least(desired_version)
module.exit_json()

out, err = capfd.readouterr()
return_val = json.loads(out)

assert return_val.get("exception") is None
assert return_val.get("invocation") is not None
if at_least:
assert return_val.get("failed") is None
else:
assert return_val.get("failed")
# The message is generated by Ansible, don't test for an exact
# message
assert desired_version in return_val.get("msg")
assert "botocore" in return_val.get("msg")
assert return_val.get("boto3_version") == DUMMY_VERSION
assert return_val.get("botocore_version") == compare_version

# ========================================================
# Test require_boto3_at_least
# ========================================================
@pytest.mark.parametrize("stdin, desired_version, compare_version, at_least", [({}, *d) for d in TEST_VERSIONS], indirect=["stdin"])
def test_require_boto3_at_least(self, monkeypatch, stdin, desired_version, compare_version, at_least, capfd):
monkeypatch.setattr(botocore, "__version__", DUMMY_VERSION)
# Set boto3 version to a known value (tests are on both sides) to make
# sure we're comparing the right library
monkeypatch.setattr(boto3, "__version__", compare_version)

# Create a minimal module that we can call
module = AnsibleAWSModule(argument_spec=dict())

with pytest.raises(SystemExit) as e:
module.require_boto3_at_least(desired_version)
module.exit_json()

out, err = capfd.readouterr()
return_val = json.loads(out)

assert return_val.get("exception") is None
assert return_val.get("invocation") is not None
if at_least:
assert return_val.get("failed") is None
else:
assert return_val.get("failed")
# The message is generated by Ansible, don't test for an exact
# message
assert desired_version in return_val.get("msg")
assert "boto3" in return_val.get("msg")
assert return_val.get("botocore_version") == DUMMY_VERSION
assert return_val.get("boto3_version") == compare_version

# ========================================================
# Test require_botocore_at_least with reason
# ========================================================
@pytest.mark.parametrize("stdin, desired_version, compare_version, at_least", [({}, *d) for d in TEST_VERSIONS], indirect=["stdin"])
def test_require_botocore_at_least_with_reason(self, monkeypatch, stdin, desired_version, compare_version, at_least, capfd):
monkeypatch.setattr(botocore, "__version__", compare_version)
# Set boto3 version to a known value (tests are on both sides) to make
# sure we're comparing the right library
monkeypatch.setattr(boto3, "__version__", DUMMY_VERSION)

reason = 'testing in progress'

# Create a minimal module that we can call
module = AnsibleAWSModule(argument_spec=dict())

with pytest.raises(SystemExit) as e:
module.require_botocore_at_least(desired_version, reason=reason)
module.exit_json()

out, err = capfd.readouterr()
return_val = json.loads(out)

assert return_val.get("exception") is None
assert return_val.get("invocation") is not None
if at_least:
assert return_val.get("failed") is None
else:
assert return_val.get("failed")
# The message is generated by Ansible, don't test for an exact
# message
assert desired_version in return_val.get("msg")
assert " {0}".format(reason) in return_val.get("msg")
assert "botocore" in return_val.get("msg")
assert return_val.get("boto3_version") == DUMMY_VERSION
assert return_val.get("botocore_version") == compare_version

# ========================================================
# Test require_boto3_at_least with reason
# ========================================================
@pytest.mark.parametrize("stdin, desired_version, compare_version, at_least", [({}, *d) for d in TEST_VERSIONS], indirect=["stdin"])
def test_require_boto3_at_least_with_reason(self, monkeypatch, stdin, desired_version, compare_version, at_least, capfd):
monkeypatch.setattr(botocore, "__version__", DUMMY_VERSION)
# Set boto3 version to a known value (tests are on both sides) to make
# sure we're comparing the right library
monkeypatch.setattr(boto3, "__version__", compare_version)

reason = 'testing in progress'

# Create a minimal module that we can call
module = AnsibleAWSModule(argument_spec=dict())

with pytest.raises(SystemExit) as e:
module.require_boto3_at_least(desired_version, reason=reason)
module.exit_json()

out, err = capfd.readouterr()
return_val = json.loads(out)

assert return_val.get("exception") is None
assert return_val.get("invocation") is not None
if at_least:
assert return_val.get("failed") is None
else:
assert return_val.get("failed")
# The message is generated by Ansible, don't test for an exact
# message
assert desired_version in return_val.get("msg")
assert " {0}".format(reason) in return_val.get("msg")
assert "boto3" in return_val.get("msg")
assert return_val.get("botocore_version") == DUMMY_VERSION
assert return_val.get("boto3_version") == compare_version