class AwsLimit(object):

    def __init__(self, name, service, default_limit,
                 def_warning_threshold, def_critical_threshold,
                 limit_type=None, limit_subtype=None,
                 ta_service_name=None, ta_limit_name=None,
                 quotas_service_code=None, quotas_name=None,
                 quotas_unit='None', quotas_unit_converter=None):
        """
        Describes one specific AWS service limit, as well as its
        current utilization, default limit, thresholds, and any
        Trusted Advisor information about this limit.

        :param name: the name of this limit (may contain spaces);
          if possible, this should be the name used by AWS, i.e. TrustedAdvisor
        :type name: str
        :param service: the :py:class:`~._AwsService` class that
          this limit is for
        :type service: :py:class:`~._AwsService`
        :param default_limit: the default value of this limit for new accounts
        :type default_limit: :py:obj:`int`, or ``None`` if unlimited
        :param def_warning_threshold: the default warning threshold, as an
          integer percentage.
        :type def_warning_threshold: int
        :param def_critical_threshold: the default critical threshold, as an
          integer percentage.
        :type def_critical_threshold: int
        :param limit_type: the type of resource this limit describes, specified
          as one of the type names used in
          `CloudFormation <http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-template-resource-type-ref.html>`_  # noqa
          such as "AWS::EC2::Instance" or "AWS::RDS::DBSubnetGroup".
        :param limit_subtype: resource sub-type for this limit, if applicable,
          such as "t2.micro" or "SecurityGroup"
        :type limit_subtype: str
        :param ta_service_name: The service name returned by Trusted Advisor
          for this limit, if different from the name of ``service``
        :type ta_service_name: str
        :param ta_limit_name: The limit name returned by Trusted Advisor for
          this limit, if different from ``name``.
        :type ta_limit_name: str
        :param quotas_service_code: the Service Quotas service code to
          retrieve this limit from, if different from the
          :py:attr:`~._AwsService.quotas_service_code` attribute of
          ``service``.
        :type quotas_service_code: str or None
        :param quotas_name: the Service Quotas quota name to use for this
          limit, if different from the limit ``name``.
        :type quotas_name: str or None
        :param quotas_unit: the Service Quotas quota unit that we need our
          limit value to be, for quotas that use units. This must be one of
          the units supported by :py:class:`~.ServiceQuotasClient`. It defaults
          to the string "None", which (for some strange reason) is what's
          returned by the Service Quotas API.
        :type quotas_unit: str
        :param quotas_unit_converter: A callable to be passed to
          :py:meth:`~.ServiceQuotasClient.get_quota_value` for unit conversion.
          Must take three positional arguments: the Service Quotas value for
          this quota (float), the quota ``Unit`` str, and the return value of
          :py:meth:`~.quotas_unit`. This callable is responsible for converting
          the quota value from the quota Unit to this class's expected unit.
          If they cannot be converted, it should log an error and return None.
        :type quotas_unit_converter: ``callable``
        :raises: ValueError
        """
        if def_warning_threshold >= def_critical_threshold:
            raise ValueError("critical threshold must be greater than warning "
                             "threshold")
        self.name = name
        self.service = service
        self.default_limit = default_limit
        self.limit_type = limit_type
        self.limit_subtype = limit_subtype
        self.limit_override = None
        self.override_ta = True
        self.ta_limit = None
        self.ta_unlimited = False
        self.api_limit = None
        self._current_usage = []
        self.def_warning_threshold = def_warning_threshold
        self.def_critical_threshold = def_critical_threshold
        self.warn_percent = None
        self.warn_count = None
        self.crit_percent = None
        self.crit_count = None
        self._warnings = []
        self._criticals = []
        self._ta_service_name = ta_service_name
        self._ta_limit_name = ta_limit_name
        self._quotas_service_code = quotas_service_code
        self._quotas_name = quotas_name
        self._quotas_unit = quotas_unit
        self.quotas_limit = None
        self.quotas_unit_converter = quotas_unit_converter

    def set_limit_override(self, limit_value, override_ta=True):
        """
        Set a new value for this limit, to override the default
        (such as when AWS Support has increased a limit of yours).
        If ``override_ta`` is True, this value will also supersede
        any found through Trusted Advisor.

        :param limit_value: the new limit value
        :type limit_value: int
        :param override_ta: whether or not to also override Trusted
          Advisor information
        :type override_ta: bool
        """
        self.limit_override = limit_value
        self.override_ta = override_ta

    def _set_ta_limit(self, limit_value):
        """
        Set the value for the limit as reported by Trusted Advisor.

        This method should only be called by :py:class:`~.TrustedAdvisor`.

        :param limit_value: the Trusted Advisor limit value
        :type limit_value: int
        """
        self.ta_limit = limit_value

    def _set_ta_unlimited(self):
        """
        Set state to indicate that TrustedAdvisor reports this limit as
        having no maximum (unlimited).

        This method should only be called by :py:class:`~.TrustedAdvisor`.
        """
        self.ta_unlimited = True

    def _set_api_limit(self, limit_value):
        """
        Set the value for the limit as reported by the service's API.

        This method should only be called from the Service class.

        :param limit_value: the API limit value
        :type limit_value: int
        """
        self.api_limit = limit_value

    def _set_quotas_limit(self, limit_value):
        """
        Set the value for the limit as reported by the Service Quotas service.

        This method should only be called from the Service class.

        :param limit_value: the Service Quotas limit value
        :type limit_value: float
        """
        self.quotas_limit = limit_value

    def get_limit_source(self):
        """
        Return :py:data:`~awslimitchecker.limit.SOURCE_DEFAULT` if
        :py:meth:`~.get_limit` returns the default limit,
        :py:data:`~awslimitchecker.limit.SOURCE_OVERRIDE` if it returns a
        manually-overridden limit,
        :py:data:`~awslimitchecker.limit.SOURCE_TA` if it returns a limit from
        Trusted Advisor, :py:data:`~awslimitchecker.limit.SOURCE_API` if it
        returns a limit retrieved from the service's API, or
        :py:data:`~.SOURCE_QUOTAS` if it returns a limit from the Service
        Quotas service.

        :returns: one of :py:data:`~awslimitchecker.limit.SOURCE_DEFAULT`,
          :py:data:`~awslimitchecker.limit.SOURCE_OVERRIDE`, or
          :py:data:`~awslimitchecker.limit.SOURCE_TA`, or
          :py:data:`~awslimitchecker.limit.SOURCE_API`, or
          :py:data:`~.awslimitchecker.limit.SOURCE_QUOTAS`
        :rtype: int
        """
        if self.limit_override is not None and (
                self.override_ta is True or
                (self.ta_limit is None and self.ta_unlimited is False)
        ):
            return SOURCE_OVERRIDE
        if self.api_limit is not None:
            return SOURCE_API
        if self.quotas_limit is not None:
            return SOURCE_QUOTAS
        if self.ta_limit is not None or self.ta_unlimited is True:
            return SOURCE_TA
        return SOURCE_DEFAULT

    def get_limit(self):
        """
        Returns the effective limit value for this Limit,
        taking into account limit overrides and Trusted
        Advisor data. None is returned for limits that are
        explicitly unlimited.

        :returns: effective limit value, ``int`` or ``None``
        """
        limit_type = self.get_limit_source()
        if limit_type == SOURCE_OVERRIDE:
            return self.limit_override
        elif limit_type == SOURCE_API:
            return self.api_limit
        elif limit_type == SOURCE_QUOTAS:
            return self.quotas_limit
        elif limit_type == SOURCE_TA:
            if self.ta_unlimited is True:
                return None
            return self.ta_limit
        return self.default_limit

    def has_resource_limits(self):
        """
        Determines if this limit contains usages with a specified maximum.
        Some AWS resources have a limit that is a different for each item.

        :returns: whether of not some resources have a defined maximum
        :rtype: bool
        """
        return any(usage for usage in self._current_usage if
                   usage.get_maximum())

    def get_current_usage(self):
        """
        Get the current usage for this limit, as a list of
        :py:class:`~.AwsLimitUsage` instances.

        :returns: list of current usage values
        :rtype: :py:obj:`list` of :py:class:`~.AwsLimitUsage`
        """
        return self._current_usage

    def get_current_usage_str(self):
        """
        Get the a string describing the current usage for this limit.

        If no usage has been added for this limit, the result will be
        "<unknown>".

        If the limit has only one current usage instance, this will be
        that instance's ``AwsLimitUsage.__str__`` value.

        If the limit has more than one current usage instance, this
        will be the a string of the form ``max: X (Y)`` where ``X`` is
        the ``AwsLimitUsage.__str__`` value of the instance
        with the maximum value, and ``Y`` is a comma-separated list
        of the ``AwsLimitUsage.__str__`` values of all usage
        instances in ascending order.

        :returns: representation of current usage
        :rtype: str
        """
        if len(self._current_usage) == 0:
            return '<unknown>'
        if len(self._current_usage) == 1:
            return str(self._current_usage[0])
        lim_str = ', '.join([str(x) for x in sorted(self._current_usage)])
        s = 'max: {m} ({l})'.format(
            m=str(max(self._current_usage)),
            l=lim_str
        )
        return s

    def _add_current_usage(self, value, maximum=None, resource_id=None,
                           aws_type=None):
        """
        Add a new current usage value for this limit.

        Creates a new :py:class:`~.AwsLimitUsage` instance and
        appends it to the internal list. If more than one usage value
        is given to this service, they should have ``id`` and
        ``aws_type`` set.

        This method should only be called from the :py:class:`~._AwsService`
        instance that created and manages this Limit.

        :param value: the numeric usage value
        :type value: :py:obj:`int` or :py:obj:`float`
        :param resource_id: If there can be multiple usage values for one limit,
          an AWS ID for the resource this instance describes
        :type resource_id: str
        :param aws_type: if ``id`` is not None, the AWS resource type
          that ID represents. As a convention, we use the AWS Resource
          Type names used by
          `CloudFormation <http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-template-resource-type-ref.html>`_  # noqa
        :type aws_type: str
        """
        self._current_usage.append(
            AwsLimitUsage(
                self,
                value,
                maximum=maximum,
                resource_id=resource_id,
                aws_type=aws_type
            )
        )

    def _reset_usage(self):
        """Discard all current usage data."""
        self._current_usage = []

    def _get_thresholds(self):
        """
        Get the warning and critical thresholds for this Limit.

        Return type is a 4-tuple of:

        1. warning integer (usage) threshold, or None
        2. warning percent threshold
        3. critical integer (usage) threshold, or None
        4. critical percent threshold

        :rtype: tuple
        """
        t = (
            self.warn_count,
            self.warn_percent or self.def_warning_threshold,
            self.crit_count,
            self.crit_percent or self.def_critical_threshold,
        )
        return t

    def set_threshold_override(self, warn_percent=None, warn_count=None,
                               crit_percent=None, crit_count=None):
        """
        Override the default warning and critical thresholds used to evaluate
        this limit's usage. Theresholds can be specified as a percentage
        of the limit, or as a usage count, or both.

        **Note:** The percent thresholds (``warn_percent`` and ``crit_percent``)
        have default values that are set globally for awslimitchecker, unlike
        the count thresholds. When setting threshold overrides to quiet or
        suppress alerts for a limit, you **must** set the percent thresholds.
        If you only set overrides for the ``count`` thresholds, the percent
        thresholds will continue to be evaluated at their awslimitchecker-wide
        default, and likely prevent alerts from being suppressed.

        see :py:meth:`~.check_thresholds` for further information on threshold
        evaluation.

        :param warn_percent: new warning threshold, percentage used
        :type warn_percent: int
        :param warn_count: new warning threshold, actual count/number
        :type warn_count: int
        :param crit_percent: new critical threshold, percentage used
        :type crit_percent: int
        :param crit_count: new critical threshold, actual count/number
        :type crit_count: int
        """
        self.warn_percent = warn_percent
        self.warn_count = warn_count
        self.crit_percent = crit_percent
        self.crit_count = crit_count

    def check_thresholds(self):
        """
        Check this limit's current usage against the specified default
        thresholds, and any custom theresholds that have been set on the
        class instance. Return True if usage is within thresholds, or false if
        warning or critical thresholds have been surpassed.

        This method sets internal variables in this instance which can be
        queried via :py:meth:`~.get_warnings` and :py:meth:`~.get_criticals`
        to obtain further details about the thresholds that were crossed.

        **Note** This function returns False if *any* thresholds were crossed.
        Please be aware of this when setting threshold overrides to suppress
        alerts. Each threshold (``warn_percent``, ``warn_count``,
        ``crit_percent``, ``crit_count``) that has been set is evaluated
        individually and the result appended to a list of warnings or criticals,
        respectively. If *any* of these evaluations failed, the method returns
        False.

        :returns: False if any thresholds were crossed, True otherwise
        :rtype: bool
        """
        (warn_int, warn_pct, crit_int, crit_pct) = self._get_thresholds()
        all_ok = True
        for u in self._current_usage:
            usage = u.get_value()
            limit = u.get_maximum() or self.get_limit()
            if limit is None or limit == 0:
                continue
            pct = (usage / (limit * 1.0)) * 100
            if crit_int is not None and usage >= crit_int:
                self._criticals.append(u)
                all_ok = False
            elif pct >= crit_pct:
                self._criticals.append(u)
                all_ok = False
            elif warn_int is not None and usage >= warn_int:
                self._warnings.append(u)
                all_ok = False
            elif pct >= warn_pct:
                self._warnings.append(u)
                all_ok = False
        return all_ok

    def get_warnings(self):
        """
        Return a list of :py:class:`~.AwsLimitUsage` instances that
        crossed the warning threshold. These objects are comparable
        and can be sorted.

        :rtype: list
        """
        return self._warnings

    def get_criticals(self):
        """
        Return a list of :py:class:`~.AwsLimitUsage` instances that
        crossed the critical threshold. These objects are comparable
        and can be sorted.

        :rtype: list
        """
        return self._criticals

    @property
    def ta_service_name(self):
        """
        Return the effective Trusted Advisor service name that this limit's
        data will have. This should be ``self._ta_service_name`` if set,
        otherwise the name of ``self.service``.

        :return: Trusted Advisor service data name
        :rtype: str
        """
        if self._ta_service_name is not None:
            return self._ta_service_name
        return self.service.service_name

    @property
    def ta_limit_name(self):
        """
        Return the effective Trusted Advisor limit name that this limit's
        data will have. This should be ``self._ta_limit_name`` if set,
        otherwise ``self.name``.

        :return: Trusted Advisor limit data name
        :rtype: str
        """
        if self._ta_limit_name is not None:
            return self._ta_limit_name
        return self.name

    @property
    def quotas_service_code(self):
        """
        Return the Service Quotas service code to use for this limit.

        :return: Service Quotas service code
        :rtype: str
        """
        if self._quotas_service_code is not None:
            return self._quotas_service_code
        return self.service.quotas_service_code

    @property
    def quota_name(self):
        """
        Return the Service Quotas quota name to use for this limit.

        :return: Service Quotas quota name
        :rtype: str
        """
        if self._quotas_name is not None:
            return self._quotas_name
        return self.name

    @property
    def quotas_unit(self):
        """
        Return the Service Quotas unit to use for this limit.

        :return: Service Quotas unit
        :rtype: str
        """
        return self._quotas_unit class AwsLimitUsage(object):

    def __init__(self, limit, value, maximum=None, resource_id=None,
                 aws_type=None):
        """
        This object describes the usage of an AWS resource, with the capability
        of containing information about the resource beyond an integer usage.

        The simplest case is an account- / region-wide count, such as the
        number of running EC2 Instances, in which case a simple integer value
        is sufficient. In this case, the :py:class:`~.AwsLimit` would have one
        instance of this class for the single value.

        In more complex cases, such as the "Subnets per VPC", the limit is
        applied by AWS on multiple resources (once per VPC). In this case,
        the :py:class:`~.AwsLimit` should have one instance of this class
        per VPC, so we can determine *which* VPCs have crossed thresholds.

        AwsLimitUsage objects are comparable based on their numeric ``value``.

        :param limit: the AwsLimit that this instance describes
        :type limit: :py:class:`~.AwsLimit`
        :param value: the numeric usage value
        :type value: :py:obj:`int` or :py:obj:`float`
        :param maximum: the numeric maximum value
        :type maximum: :py:obj:`int` or :py:obj:`float`
        :param resource_id: If there can be multiple usage values for one limit,
          an AWS ID for the resource this instance describes
        :type resource_id: str
        :param aws_type: if ``id`` is not None, the AWS resource type
          that ID represents. As a convention, we use the AWS Resource
          Type names used by
          `CloudFormation <http://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-template-resource-type-ref.html>`_  # noqa
        :type aws_type: str
        """
        self.limit = limit
        self.value = value
        self.maximum = maximum
        self.resource_id = resource_id
        self.aws_type = aws_type

    def get_value(self):
        """
        Get the current usage value

        :returns: current usage value
        :rtype: :py:obj:`int` or :py:obj:`float`
        """
        return self.value

    def get_maximum(self):
        """
        Get the current maximum value

        :returns: current maximum value
        :rtype: :py:obj:`int` or :py:obj:`float`
        """
        return self.maximum

    def __str__(self):
        """
        Return a string representation of this object.

        If ``id`` is not set, return ``value`` formatted as a string;
        otherwise, return a string of the format ``id=value``.

        :rtype: str
        """
        s = '{v}'.format(v=self.value)
        if self.resource_id is not None:
            s = self.resource_id + '=' + s
        return s

    def __eq__(self, other):
        return self.value == other.value

    def __ne__(self, other):
        return self.value != other.value

    def __gt__(self, other):
        return self.value > other.value

    def __lt__(self, other):
        return self.value < other.value

    def __ge__(self, other):
        return self.value >= other.value class StoreKeyValuePair(argparse.Action):
    """
    Store key=value options in a dict as {'key': 'value'}.

    Supports specifying the option multiple times, but NOT with ``nargs``.

    See :py:class:`~argparse.Action`.
    """

    def __init__(self, option_strings, dest, nargs=None, const=None,
                 default=None, type=None, choices=None, required=False,
                 help=None, metavar=None):
        super(StoreKeyValuePair, self).__init__(option_strings, dest, nargs,
                                                const, default, type, choices,
                                                required, help, metavar)
        self.default = {}

    def __call__(self, parser, namespace, values, option_string=None):
        if '=' not in values:
            raise argparse.ArgumentError(self, 'must be in the form key=value')
        n, v = values.split('=')
        # handle quotes for values with spaces
        n = n.strip('"\'')
        getattr(namespace, self.dest)[n] = v def dict2cols(d, spaces=2, separator=' '):
    """
    Take a dict of string keys and string values, and return a string with
    them formatted as two columns separated by at least ``spaces`` number of
    ``separator`` characters.

    :param d: dict of string keys, string values
    :type d: dict
    :param spaces: number of spaces to separate columns by
    :type spaces: int
    :param separator: character to fill in between columns
    :type separator: str
    """
    if len(d) == 0:
        return ''
    s = ''
    maxlen = max([len(k) for k in d.keys()])
    fmt_str = '{k:' + separator + '<' + str(maxlen + spaces) + '}{v}\n'
    for k in sorted(d.keys()):
        s += fmt_str.format(
            k=k,
            v=d[k],
        )
    return s def paginate_dict(function_ref, *argv, **kwargs):
    """
    Paginate through a query that returns a dict result, and return the
    combined result.

    Note that this function requires some special kwargs to be passed in:

    * __alc_marker_path__ - The dictionary path to the Marker for the next
      result set. If this path does not exist, the raw result will be returned.
    * __alc_data_path__ - The dictionary path to the list containing the query
      results. This will be updated with the results of subsequent queries.
    * __alc_marker_param__ - The parameter name to pass to ``function_ref``
      with the marker value.

    These paths should be lists, in a form usable by
    :py:func:`~._get_dict_value_by_path`.

    :param function_ref: the function to call
    :type function_ref: ``function``
    :param argv: the parameters to pass to the function
    :type argv: tuple
    :param kwargs: keyword arguments to pass to the function
    :type kwargs: dict
    """
    if 'alc_marker_path' not in kwargs:
        raise Exception("alc_marker_path must be specified for queries "
                        "that return a dict.")
    if 'alc_data_path' not in kwargs:
        raise Exception("alc_data_path must be specified for queries "
                        "that return a dict.")
    if 'alc_marker_param' not in kwargs:
        raise Exception("alc_marker_param must be specified for queries "
                        "that return a dict.")

    marker_path = kwargs['alc_marker_path']
    data_path = kwargs['alc_data_path']
    marker_param = kwargs['alc_marker_param']

    # strip off "^alc_" args
    pass_kwargs = {}
    for k, v in kwargs.items():
        if not k.startswith('alc_'):
            pass_kwargs[k] = v

    # first function call
    result = function_ref(*argv, **pass_kwargs)

    # check for marker, return if not present
    marker = _get_dict_value_by_path(result, marker_path)
    if marker is None:
        return result
    logger.debug("Found marker (%s) in result; iterating for more results",
                 marker_path)
    # iterate results
    results = []
    results.extend(_get_dict_value_by_path(result, data_path))
    while marker is not None:
        logger.debug("Querying %s with %s=%s", function_ref, marker_param,
                     marker)
        pass_kwargs[marker_param] = marker
        result = function_ref(*argv, **pass_kwargs)
        data = _get_dict_value_by_path(result, data_path)
        results.extend(data)
        marker = _get_dict_value_by_path(result, marker_path)
    # drop the full results into the last result response
    res = _set_dict_value_by_path(result, results, data_path)
    return res def _get_dict_value_by_path(d, path):
    """
    Given a dict (``d``) and a list specifying the hierarchical path to a key
    in that dict (``path``), return the value at that path or None if it does
    not exist.

    :param d: the dict to search in
    :type d: dict
    :param path: the path to the key in the dict
    :type path: list
    """
    tmp_path = deepcopy(path)
    try:
        while len(tmp_path) > 0:
            k = tmp_path.pop(0)
            d = d[k]
        return d
    except Exception:
        return None def _set_dict_value_by_path(d, val, path):
    """
    Given a dict (``d``), a value (``val``),  and a list specifying the
    hierarchical path to a key in that dict (``path``), set the value in ``d``
    at ``path`` to ``val``.

    :param d: the dict to search in
    :type d: dict
    :param path: the path to the key in the dict
    :type path: list
    :raises: TypeError if the path is too short
    :returns: the modified dict
    """
    tmp_path = deepcopy(path)
    tmp_d = deepcopy(d)
    result = tmp_d
    while len(tmp_path) > 0:
        if len(tmp_path) == 1:
            result[tmp_path[0]] = val
            break
        k = tmp_path.pop(0)
        result = result[k]
    return tmp_d def _get_latest_version():
    """
    Attempt to retrieve the latest awslimitchecker version from PyPI, timing
    out after 4 seconds. If the version can be retrieved and is greater than
    the currently running version, return it as a string. If the version cannot
    be retrieved or is not greater than the currently running version, return
    None.

    This function MUST not ever raise an exception.

    :return: latest version from PyPI, if newer than current version
    :rtype: `str` or `None`
    """
    try:
        http = urllib3.PoolManager()
        r = http.request(
            'GET', 'https://pypi.org/pypi/awslimitchecker/json',
            timeout=4.0, headers={
                'User-Agent': 'github.com/jantman/awslimitchecker '
                              '%s' % _VERSION
            }
        )
        assert r.status == 200, "PyPI responded HTTP %s" % r.status
        j = json.loads(r.data)
        latest = tuple([
            int(i) for i in j['info']['version'].split('.')[0:3]
        ])
        if latest > _VERSION_TUP:
            return j['info']['version']
    except Exception:
        logger.debug('Error getting latest version from PyPI', exc_info=True)
    return None def color_output(s, color, colorize=True):
    if not colorize:
        return s
    return termcolor.colored(s, color) def issue_string_tuple(service_name, limit, crits, warns, colorize=True):
    """
    Return a 2-tuple of key (service/limit name)/value (usage) strings
    describing a limit that has crossed its threshold.

    :param service_name: the name of the service
    :type service_name: str
    :param limit: the Limit this relates to
    :type limit: :py:class:`~.AwsLimit`
    :param crits: the specific usage values that crossed the critical
      threshold
    :type usage: :py:obj:`list` of :py:class:`~.AwsLimitUsage`
    :param crits: the specific usage values that crossed the warning
      threshold
    :type usage: :py:obj:`list` of :py:class:`~.AwsLimitUsage`
    :param colorize: whether or not to colorize output; passed through to
      :py:func:`~.color_output`.
    :type colorize: bool
    :returns: 2-tuple of strings describing crossed thresholds,
      first describing the service and limit name and second listing the
      limit and usage
    :rtype: tuple
    """
    usage_str = ''
    if len(crits) > 0:
        tmp = 'CRITICAL: '
        tmp += ', '.join([str(x) for x in sorted(crits)])
        usage_str += color_output(tmp, 'red', colorize=colorize)
    if len(warns) > 0:
        if len(crits) > 0:
            usage_str += ' '
        tmp = 'WARNING: '
        tmp += ', '.join([str(x) for x in sorted(warns)])
        usage_str += color_output(tmp, 'yellow', colorize=colorize)
    k = "{s}/{l}".format(
        s=service_name,
        l=limit.name,
    )
    v = "(limit {v}) {u}".format(
        v=limit.get_limit(),
        u=usage_str,
    )
    return k, v
import argparse
import pytest
import sys
import termcolor
from awslimitchecker.limit import AwsLimit, AwsLimitUsage
from awslimitchecker.utils import (
    StoreKeyValuePair, dict2cols, paginate_dict, _get_dict_value_by_path,
    _set_dict_value_by_path, _get_latest_version, color_output,
    issue_string_tuple
)
    from mock import call, Mock, patch
    from unittest.mock import call, Mock, patch
    def test_not_colored(self):
        assert color_output(
            'foo', 'yellow', colorize=False
        ) == 'foo'


class TestIssueStringTuple(object):

    def test_crit_one(self):
        mock_limit = Mock(spec_set=AwsLimit)
        type(mock_limit).name = 'limitname'
        mock_limit.get_limit.return_value = 12

        c1 = AwsLimitUsage(mock_limit, 56)

        def se_color(s, c, colorize=True):
            return 'xX%sXx' % s

        with patch('%s.color_output' % pbm) as m_co:
            m_co.side_effect = se_color
            res = issue_string_tuple(
                'svcname',
                mock_limit,
                [c1],
                []
            )
        assert res == ('svcname/limitname',
                       '(limit 12) xXCRITICAL: 56Xx')
        assert m_co.mock_calls == [
            call('CRITICAL: 56', 'red', colorize=True)
        ]

    def test_crit_multi(self):
        mock_limit = Mock(spec_set=AwsLimit)
        type(mock_limit).name = 'limitname'
        mock_limit.get_limit.return_value = 5

        c1 = AwsLimitUsage(mock_limit, 10)
        c2 = AwsLimitUsage(mock_limit, 12, resource_id='c2id')
        c3 = AwsLimitUsage(mock_limit, 8)

        def se_color(s, c, colorize=True):
            return 'xX%sXx' % s

        with patch('%s.color_output' % pbm) as m_co:
            m_co.side_effect = se_color
            res = issue_string_tuple(
                'svcname',
                mock_limit,
                [c1, c2, c3],
                []
            )
        assert res == ('svcname/limitname',
                       '(limit 5) xXCRITICAL: 8, 10, c2id=12Xx')
        assert m_co.mock_calls == [
            call('CRITICAL: 8, 10, c2id=12', 'red', colorize=True)
        ]

    def test_warn_one(self):
        mock_limit = Mock(spec_set=AwsLimit)
        type(mock_limit).name = 'limitname'
        mock_limit.get_limit.return_value = 12

        w1 = AwsLimitUsage(mock_limit, 11)

        def se_color(s, c, colorize=True):
            return 'xX%sXx' % s

        with patch('%s.color_output' % pbm) as m_co:
            m_co.side_effect = se_color
            res = issue_string_tuple(
                'svcname',
                mock_limit,
                [],
                [w1]
            )
        assert res == ('svcname/limitname', '(limit 12) xXWARNING: 11Xx')
        assert m_co.mock_calls == [
            call('WARNING: 11', 'yellow', colorize=True)
        ]

    def test_warn_multi(self):
        mock_limit = Mock(spec_set=AwsLimit)
        type(mock_limit).name = 'limitname'
        mock_limit.get_limit.return_value = 12

        w1 = AwsLimitUsage(mock_limit, 11)
        w2 = AwsLimitUsage(mock_limit, 10, resource_id='w2id')
        w3 = AwsLimitUsage(mock_limit, 10, resource_id='w3id')

        def se_color(s, c, colorize=True):
            return 'xX%sXx' % s

        with patch('%s.color_output' % pbm) as m_co:
            m_co.side_effect = se_color
            res = issue_string_tuple(
                'svcname',
                mock_limit,
                [],
                [w1, w2, w3]
            )
        assert res == ('svcname/limitname',
                       '(limit 12) xXWARNING: w2id=10, w3id=10, 11Xx')
        assert m_co.mock_calls == [
            call('WARNING: w2id=10, w3id=10, 11', 'yellow', colorize=True)
        ]

    def test_both_one(self):
        mock_limit = Mock(spec_set=AwsLimit)
        type(mock_limit).name = 'limitname'
        mock_limit.get_limit.return_value = 12

        c1 = AwsLimitUsage(mock_limit, 10)
        w1 = AwsLimitUsage(mock_limit, 10, resource_id='w3id')

        def se_color(s, c, colorize=True):
            return 'xX%sXx' % s

        with patch('%s.color_output' % pbm) as m_co:
            m_co.side_effect = se_color

            res = issue_string_tuple(