Skip to content

Commit

Permalink
Merge pull request #124 from /issues/114
Browse files Browse the repository at this point in the history
Issues/114 - expanded integration tests
  • Loading branch information
jantman committed Feb 17, 2016
2 parents 0cba817 + 1ebc8c0 commit a2a4784
Show file tree
Hide file tree
Showing 11 changed files with 789 additions and 72 deletions.
1 change: 1 addition & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ Pre-release (develop branch)
* `#112 <https://github.com/jantman/awslimitchecker/issues/112>`_ fix a bug in the versioncheck integration tests, and a bug uncovered in versioncheck itself, both dealing with checkouts that are on a un-cloned branch.
* `#105 <https://github.com/jantman/awslimitchecker/issues/105>`_ build and upload wheels in addition to sdist
* `#95 <https://github.com/jantman/awslimitchecker/issues/95>`_ **major** refactor to convert AWS client library from `boto <https://github.com/boto/boto>`_ to `boto3 <https://github.com/boto/boto3>`_. This also includes significant changes to the internal connection logic and some of the internal (private) API. Pagination has been moved to boto3 wherever possible, and handling of API request throttling has been removed from awslimitchecker, as boto3 handles this itself. This also introduces full, official support for python3.
* `#114 <https://github.com/jantman/awslimitchecker/issues/114>`_ expanded automatic integration tests
* **Please note** that version 0.3.0 of awslimitchecker moved from using ``boto`` as its AWS API client to using ``boto3``. This change is mostly transparent, but there is a minor change in how AWS credentials are handled. In ``boto``, if the ``AWS_ACCESS_KEY_ID`` and ``AWS_SECRET_ACCESS_KEY`` environment variables were set, and the region was not set explicitly via awslimitchecker, the AWS region would either be taken from the ``AWS_DEFAULT_REGION`` environment variable or would default to us-east-1, regardless of whether a configuration file (``~/.aws/credentials`` or ``~/.aws/config``) was present. With boto3, it appears that the default region from the configuration file will be used if present, regardless of whether the credentials come from that file or from environment variables.

0.2.3 (2015-12-16)
Expand Down
22 changes: 14 additions & 8 deletions awslimitchecker/connectable.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ def _boto3_connection_kwargs(self):
Generate keyword arguments for boto3 connection functions.
If ``self.account_id`` is None, this will just include
``region_name=self.region``. Otherwise, call
:py:meth:`~._get_sts_token_boto3` to get STS token credentials using
:py:meth:`~._get_sts_token` to get STS token credentials using
`boto3.STS.Client.assume_role <https://boto3.readthedocs.org/en/
latest/reference/services/sts.html#STS.Client.assume_role>`_ and include
those credentials in the return value.
Expand All @@ -91,7 +91,7 @@ def _boto3_connection_kwargs(self):
logger.debug("Connecting for account %s role '%s' with STS "
"(region: %s)", self.account_id, self.account_role,
self.region)
Connectable.credentials = self._get_sts_token_boto3()
Connectable.credentials = self._get_sts_token()
else:
logger.debug("Reusing previous STS credentials for account %s",
self.account_id)
Expand Down Expand Up @@ -137,7 +137,7 @@ def connect_resource(self):
logger.info("Connected to %s (resource) in region %s", self.api_name,
self.resource_conn.meta.client._client_config.region_name)

def _get_sts_token_boto3(self):
def _get_sts_token(self):
"""
Assume a role via STS and return the credentials.
Expand All @@ -156,11 +156,17 @@ def _get_sts_token_boto3(self):
sts = boto3.client('sts', region_name=self.region)
arn = "arn:aws:iam::%s:role/%s" % (self.account_id, self.account_role)
logger.debug("STS assume role for %s", arn)
role = sts.assume_role(RoleArn=arn,
RoleSessionName="awslimitchecker",
ExternalId=self.external_id,
SerialNumber=self.mfa_serial_number,
TokenCode=self.mfa_token)
assume_kwargs = {
'RoleArn': arn,
'RoleSessionName': 'awslimitchecker'
}
if self.external_id is not None:
assume_kwargs['ExternalId'] = self.external_id
if self.mfa_serial_number is not None:
assume_kwargs['SerialNumber'] = self.mfa_serial_number
if self.mfa_token is not None:
assume_kwargs['TokenCode'] = self.mfa_token
role = sts.assume_role(**assume_kwargs)
creds = ConnectableCredentials(role)
logger.debug("Got STS credentials for role; access_key_id=%s",
creds.access_key)
Expand Down
123 changes: 123 additions & 0 deletions awslimitchecker/tests/conftest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
"""
awslimitchecker/tests/conftest.py
This alters the pytest output to suppress some strings that come in as
environment variables.
This code is inspired by / based on the
`pytest-wholenodeid <https://github.com/willkg/pytest-wholenodeid>`_ plugin by
`Will Kahn-Greene <https://github.com/willkg>`_, distributed under a
Simplified BSD License.
The latest version of this package is available at:
<https://github.com/jantman/awslimitchecker>
################################################################################
Copyright 2015 Jason Antman <jason@jasonantman.com> <http://www.jasonantman.com>
This file is part of awslimitchecker, also known as awslimitchecker.
awslimitchecker is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
awslimitchecker is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with awslimitchecker. If not, see <http://www.gnu.org/licenses/>.
The Copyright and Authors attributions contained herein may not be removed or
otherwise altered, except to add the Author attribution of a contributor to
this work. (Additional Terms pursuant to Section 7b of the AGPL v3)
################################################################################
While not legally required, I sincerely request that anyone who finds
bugs please submit them at <https://github.com/jantman/awslimitchecker> or
to me via email, and that you send any contributions or improvements
either as a pull request on GitHub, or to me via email.
################################################################################
AUTHORS:
Jason Antman <jason@jasonantman.com> <http://www.jasonantman.com>
################################################################################
"""

import sys
import os
import re

from _pytest.terminal import TerminalReporter
import pytest


class OutputSanitizer(object):

def __init__(self, tw):
self.mfa_re = re.compile(r"'mfa_token':\s*b?'\d+'")
self.tc_re = re.compile(r"'TokenCode':\s*b?'\d+'")
self._tw = tw
self.replace = []
for keyname in [
'AWS_MAIN_ACCESS_KEY_ID',
'AWS_MAIN_SECRET_ACCESS_KEY',
'AWS_MASTER_ACCOUNT_ID',
'AWS_EXTERNAL_ID',
'AWS_INTEGRATION_ACCESS_KEY_ID',
'AWS_INTEGRATION_SECRET_KEY',
'AWS_MFA_INTEGRATION_ACCESS_KEY_ID',
'AWS_MFA_INTEGRATION_SECRET_KEY',
'AWS_MFA_SERIAL',
'AWS_MFA_SECRET',
'AWS_MFA_EXTERNAL_ID',
'AWS_MFA3_INTEGRATION_ACCESS_KEY_ID',
'AWS_MFA3_INTEGRATION_SECRET_KEY',
'AWS_MFA3_SERIAL',
'AWS_MFA3_SECRET'
]:
if keyname in os.environ:
self.replace.append((
os.environ[keyname],
"<<os.environ[%s]>>" % keyname
))

def line(self, s='', **kw):
line = self.sanitize_line(s)
self._tw.line(line, **kw)

def sanitize_line(self, line):
for repl_set in self.replace:
line = line.replace(repl_set[0], repl_set[1])
line = self.mfa_re.sub("'mfa_token': 'XXXXXX", line)
line = self.tc_re.sub("'TokenCode': 'XXXXXX", line)
return line

def sep(self, *args, **kwargs):
self._tw.sep(*args, **kwargs)

@property
def fullwidth(self):
return self._tw.fullwidth


class WholeNodeIDTerminalReporter(TerminalReporter):

def _outrep_summary(self, rep):
sanitizer = OutputSanitizer(self._tw)
rep.toterminal(sanitizer)
for secname, content in rep.sections:
self._tw.sep("-", secname)
if content[-1:] == "\n":
content = content[:-1]
sanitizer.line(content)


@pytest.mark.trylast
def pytest_configure(config):
# Get the standard terminal reporter plugin and replace it with our
standard_reporter = config.pluginmanager.getplugin('terminalreporter')
wholenodeid_reporter = WholeNodeIDTerminalReporter(config, sys.stdout)
config.pluginmanager.unregister(standard_reporter)
config.pluginmanager.register(wholenodeid_reporter, 'terminalreporter')
103 changes: 103 additions & 0 deletions awslimitchecker/tests/support.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,109 @@
"""

from awslimitchecker.limit import AwsLimit
import logging


class LogRecordHelper(object):
"""class to help working with an array of LogRecords"""

levelmap = {
logging.CRITICAL: 'critical',
logging.ERROR: 'error',
logging.WARNING: 'warning',
logging.INFO: 'info',
logging.DEBUG: 'debug',
logging.NOTSET: 'notset'
}

def __init__(self, logcapture):
"""
Initialize LogRecord helper.
:param logcapture: testfixtures.logcapture.LogCapture object
"""
self._logcapture = logcapture
self.records = logcapture.records

def get_at_level(self, lvl):
"""
Return a list of all records in order for a given numeric logging level
:param lvl: the level to get
:type lvl: int
:returns: list of LogRecord objects
"""
res = []
for rec in self.records:
if rec.levelno == lvl:
res.append(rec)
return res

def get_at_or_above_level(self, lvl):
"""
Return a list of all records in order, at OR ABOVE a given numeric
logging level
:param lvl: the level to get
:type lvl: int
:returns: list of LogRecord objects
"""
res = []
for rec in self.records:
if rec.levelno >= lvl:
res.append(rec)
return res

def assert_failed_message(self, records):
"""
Return a list of string representations of the log records, for use
in assertion failure messages.
:param records: list of LogRecord objects
:return: list of strings
"""
res = ""
for r in records:
res += '%s:%s.%s (%s:%s) %s - %s %s\n' % (
r.name,
r.module,
r.funcName,
r.filename,
r.lineno,
r.levelname,
r.msg,
r.args
)
return res

def unexpected_logs(self):
"""
Return a list of strings representing awslimitchecker log messages
in this object's log records, that shouldn't be encountered in normal
operation.
:return: list of strings representing log records
"""
res = []
msg = 'Cannot check TrustedAdvisor: %s'
args = ('AWS Premium Support Subscription is required to use this '
'service.', )
for r in self.get_at_or_above_level(logging.WARN):
if (r.levelno == logging.WARN and r.module == 'trustedadvisor' and
r.funcName == '_get_limit_check_id' and r.msg == msg and
r.args == args):
continue
res.append('%s:%s.%s (%s:%s) %s - %s %s' % (
r.name,
r.module,
r.funcName,
r.filename,
r.lineno,
r.levelname,
r.msg,
r.args
))
return res


def sample_limits():
Expand Down
Loading

0 comments on commit a2a4784

Please sign in to comment.