# Buggy code
def create_metric_alarm(connection, module):
    name = module.params.get("name")
    metric = module.params.get("metric")
    namespace = module.params.get("namespace")
    statistic = module.params.get("statistic")
    comparison = module.params.get("comparison")
    threshold = module.params.get("threshold")
    period = module.params.get("period")
    evaluation_periods = module.params.get("evaluation_periods")
    unit = module.params.get("unit")
    description = module.params.get("description")
    dimensions = module.params.get("dimensions")
    alarm_actions = module.params.get("alarm_actions")
    insufficient_data_actions = module.params.get("insufficient_data_actions")
    ok_actions = module.params.get("ok_actions")

    alarms = connection.describe_alarms(alarm_names=[name])

    if not alarms:
        alm = MetricAlarm(
            name=name,
            metric=metric,
            namespace=namespace,
            statistic=statistic,
            comparison=comparison,
            threshold=threshold,
            period=period,
            evaluation_periods=evaluation_periods,
            unit=unit,
            description=description,
            dimensions=dimensions,
            alarm_actions=alarm_actions,
            insufficient_data_actions=insufficient_data_actions,
            ok_actions=ok_actions,
        )
        try:
            connection.create_alarm(alm)
            changed = True
            alarms = connection.describe_alarms(alarm_names=[name])
        except BotoServerError as e:
            module.fail_json(msg=str(e))

    else:
        alarm = alarms[0]
        changed = False

        for attr in (
            "comparison",
            "metric",
            "namespace",
            "statistic",
            "threshold",
            "period",
            "evaluation_periods",
            "unit",
            "description",
        ):
            if getattr(alarm, attr) != module.params.get(attr):
                changed = True
                setattr(alarm, attr, module.params.get(attr))
        # this is to deal with a current bug where you cannot assign '<=>' to the comparator when modifying an existing alarm
        comparison = alarm.comparison
        comparisons = {
            "<=": "LessThanOrEqualToThreshold",
            "<": "LessThanThreshold",
            ">=": "GreaterThanOrEqualToThreshold",
            ">": "GreaterThanThreshold",
        }
        alarm.comparison = comparisons[comparison]

        dim1 = module.params.get("dimensions")
        dim2 = alarm.dimensions

        for keys in dim1:
            if not isinstance(dim1[keys], list):
                dim1[keys] = [dim1[keys]]
            if keys not in dim2 or dim1[keys] != dim2[keys]:
                changed = True
                setattr(alarm, "dimensions", dim1)

        for attr in ("alarm_actions", "insufficient_data_actions", "ok_actions"):
            action = module.params.get(attr) or []
            # Boto and/or ansible may provide same elements in lists but in different order.
            # Compare on sets since they do not need any order.
            if set(getattr(alarm, attr)) != set(action):
                changed = True
                setattr(alarm, attr, module.params.get(attr))

        try:
            if changed:
                connection.create_alarm(alarm)
        except BotoServerError as e:
            module.fail_json(msg=str(e))
    result = alarms[0]
    module.exit_json(
        changed=changed,
        name=result.name,
        actions_enabled=result.actions_enabled,
        alarm_actions=result.alarm_actions,
        alarm_arn=result.alarm_arn,
        comparison=result.comparison,
        description=result.description,
        dimensions=result.dimensions,
        evaluation_periods=result.evaluation_periods,
        insufficient_data_actions=result.insufficient_data_actions,
        last_updated=result.last_updated,
        metric=result.metric,
        namespace=result.namespace,
        ok_actions=result.ok_actions,
        period=result.period,
        state_reason=result.state_reason,
        state_value=result.state_value,
        statistic=result.statistic,
        threshold=result.threshold,
        unit=result.unit,
    )


# Traceback (nboto.exception.BotoServerError)
2019-04-26 00:11:00,046 p=50 u=root |  fatal: [localhost]: FAILED! => {"changed": false, "module_stderr": "Traceback (most recent call last):\n  File \"/tmp/ansible_y5VU_q/ansible_module_ec2_metric_alarm.py\", line 327, in <module>\n    main()\n  File \"/tmp/ansible_y5VU_q/ansible_module_ec2_metric_alarm.py\", line 320, in main\n    delete_metric_alarm(connection, module)\n  File \"/tmp/ansible_y5VU_q/ansible_module_ec2_metric_alarm.py\", line 263, in delete_metric_alarm\n    alarms = connection.describe_alarms(alarm_names=[name])\n  File \"/usr/local/lib/python2.7/site-packages/boto/ec2/cloudwatch/__init__.py\", line 382, in describe_alarms\n    [('MetricAlarms', MetricAlarms)])\n  File \"/usr/local/lib/python2.7/site-packages/boto/connection.py\", line 1186, in get_list\n    raise self.ResponseError(response.status, response.reason, body)\nboto.exception.BotoServerError: BotoServerError: 400 Bad Request\n<ErrorResponse xmlns=\"http://monitoring.amazonaws.com/doc/2010-08-01/\">\n  <Error>\n    <Type>Sender</Type>\n    <Code>Throttling</Code>\n    <Message>Rate exceeded</Message>\n  </Error>\n  <RequestId>c5661b3a-67b7-11e9-aeaa-6fd6e091195a</RequestId>\n</ErrorResponse>\n\n", "module_stdout": "", "msg": "MODULE FAILURE", "rc": 0}

# Fixed code
def create_metric_alarm(connection, module):
    name = module.params.get("name")
    metric = module.params.get("metric")
    namespace = module.params.get("namespace")
    statistic = module.params.get("statistic")
    comparison = module.params.get("comparison")
    threshold = module.params.get("threshold")
    period = module.params.get("period")
    evaluation_periods = module.params.get("evaluation_periods")
    unit = module.params.get("unit")
    description = module.params.get("description")
    dimensions = module.params.get("dimensions")
    alarm_actions = module.params.get("alarm_actions")
    insufficient_data_actions = module.params.get("insufficient_data_actions")
    ok_actions = module.params.get("ok_actions")

    alarms = None
    try:
        alarms = connection.describe_alarms(alarm_names=[name])
    except BotoServerError as e:
        module.fail_json(
            msg="Failed to describe alarm %s: %s" % (name, str(e)),
            exception=traceback.format_exc(),
        )

    if not alarms:
        alm = MetricAlarm(
            name=name,
            metric=metric,
            namespace=namespace,
            statistic=statistic,
            comparison=comparison,
            threshold=threshold,
            period=period,
            evaluation_periods=evaluation_periods,
            unit=unit,
            description=description,
            dimensions=dimensions,
            alarm_actions=alarm_actions,
            insufficient_data_actions=insufficient_data_actions,
            ok_actions=ok_actions,
        )
        try:
            connection.create_alarm(alm)
            changed = True
            alarms = connection.describe_alarms(alarm_names=[name])
        except BotoServerError as e:
            module.fail_json(
                msg="Failed to create alarm %s: %s" % (name, str(e)),
                exception=traceback.format_exc(),
            )

    else:
        alarm = alarms[0]
        changed = False

        for attr in (
            "comparison",
            "metric",
            "namespace",
            "statistic",
            "threshold",
            "period",
            "evaluation_periods",
            "unit",
            "description",
        ):
            if getattr(alarm, attr) != module.params.get(attr):
                changed = True
                setattr(alarm, attr, module.params.get(attr))
        # this is to deal with a current bug where you cannot assign '<=>' to the comparator when modifying an existing alarm
        comparison = alarm.comparison
        comparisons = {
            "<=": "LessThanOrEqualToThreshold",
            "<": "LessThanThreshold",
            ">=": "GreaterThanOrEqualToThreshold",
            ">": "GreaterThanThreshold",
        }
        alarm.comparison = comparisons[comparison]

        dim1 = module.params.get("dimensions")
        dim2 = alarm.dimensions

        for keys in dim1:
            if not isinstance(dim1[keys], list):
                dim1[keys] = [dim1[keys]]
            if keys not in dim2 or dim1[keys] != dim2[keys]:
                changed = True
                setattr(alarm, "dimensions", dim1)

        for attr in ("alarm_actions", "insufficient_data_actions", "ok_actions"):
            action = module.params.get(attr) or []
            # Boto and/or ansible may provide same elements in lists but in different order.
            # Compare on sets since they do not need any order.
            if set(getattr(alarm, attr)) != set(action):
                changed = True
                setattr(alarm, attr, module.params.get(attr))

        try:
            if changed:
                connection.create_alarm(alarm)
        except BotoServerError as e:
            module.fail_json(msg=str(e))
    result = alarms[0]
    module.exit_json(
        changed=changed,
        name=result.name,
        actions_enabled=result.actions_enabled,
        alarm_actions=result.alarm_actions,
        alarm_arn=result.alarm_arn,
        comparison=result.comparison,
        description=result.description,
        dimensions=result.dimensions,
        evaluation_periods=result.evaluation_periods,
        insufficient_data_actions=result.insufficient_data_actions,
        last_updated=result.last_updated,
        metric=result.metric,
        namespace=result.namespace,
        ok_actions=result.ok_actions,
        period=result.period,
        state_reason=result.state_reason,
        state_value=result.state_value,
        statistic=result.statistic,
        threshold=result.threshold,
        unit=result.unit,
    )