Skip to content

Commit

Permalink
Add new "require_boto(3|core)_at_least() helpers (ansible-collections…
Browse files Browse the repository at this point in the history
…#446)

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

SUMMARY
We currently have lots of different ways we output that we needed a library, add a helper for our standard use-case of minimum botocore versions for a feature.
ISSUE TYPE

Feature Pull Request

COMPONENT NAME
plugins/module_utils/core.py
plugins/modules/ec2_vol.py
ADDITIONAL INFORMATION

Reviewed-by: Alina Buzachis <None>
Reviewed-by: None <None>
  • Loading branch information
tremble authored Aug 9, 2021
1 parent d198375 commit 2574ca4
Show file tree
Hide file tree
Showing 7 changed files with 259 additions and 5 deletions.
38 changes: 38 additions & 0 deletions plugins/module_utils/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,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 @@ -263,6 +282,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 @@ -717,8 +717,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

0 comments on commit 2574ca4

Please sign in to comment.