# Buggy code
def main():
    argument_spec = ec2_argument_spec()
    argument_spec.update(
        dict(
            state=dict(required=True, choices=["present", "absent"]),
            arn=dict(required=False, type="str"),
            family=dict(required=False, type="str"),
            revision=dict(required=False, type="int"),
            containers=dict(required=False, type="list"),
            network_mode=dict(
                required=False,
                default="bridge",
                choices=["bridge", "host", "none"],
                type="str",
            ),
            task_role_arn=dict(required=False, default="", type="str"),
            volumes=dict(required=False, type="list"),
        )
    )

    module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True)

    if not HAS_BOTO:
        module.fail_json(msg="boto is required.")

    if not HAS_BOTO3:
        module.fail_json(msg="boto3 is required.")

    task_to_describe = None
    task_mgr = EcsTaskManager(module)
    results = dict(changed=False)

    for container in module.params["containers"]:
        if "environment" in container:
            for environment in container["environment"]:
                environment["value"] = str(environment["value"])

    if module.params["state"] == "present":
        if "containers" not in module.params or not module.params["containers"]:
            module.fail_json(
                msg="To use task definitions, a list of containers must be specified"
            )

        if "family" not in module.params or not module.params["family"]:
            module.fail_json(msg="To use task definitions, a family must be specified")

        family = module.params["family"]
        existing_definitions_in_family = task_mgr.describe_task_definitions(
            module.params["family"]
        )

        if "revision" in module.params and module.params["revision"]:
            # The definition specifies revision. We must gurantee that an active revision of that number will result from this.
            revision = int(module.params["revision"])

            # A revision has been explicitly specified. Attempt to locate a matching revision
            tasks_defs_for_revision = [
                td
                for td in existing_definitions_in_family
                if td["revision"] == revision
            ]
            existing = (
                tasks_defs_for_revision[0] if len(tasks_defs_for_revision) > 0 else None
            )

            if existing and existing["status"] != "ACTIVE":
                # We cannot reactivate an inactive revision
                module.fail_json(
                    msg="A task in family '%s' already exists for revsion %d, but it is inactive"
                    % (family, revision)
                )
            elif not existing:
                if not existing_definitions_in_family and revision != 1:
                    module.fail_json(
                        msg="You have specified a revision of %d but a created revision would be 1"
                        % revision
                    )
                elif (
                    existing_definitions_in_family
                    and existing_definitions_in_family[-1]["revision"] + 1 != revision
                ):
                    module.fail_json(
                        msg="You have specified a revision of %d but a created revision would be %d"
                        % (revision, existing_definitions_in_family[-1]["revision"] + 1)
                    )
        else:
            existing = None

            def _right_has_values_of_left(left, right):
                # Make sure the values are equivalent for everything left has
                for k, v in left.items():
                    if not (
                        (not v and (k not in right or not right[k]))
                        or (k in right and v == right[k])
                    ):
                        # We don't care about list ordering because ECS can change things
                        if isinstance(v, list) and k in right:
                            left_list = v
                            right_list = right[k] or []

                            if len(left_list) != len(right_list):
                                return False

                            for list_val in left_list:
                                if list_val not in right_list:
                                    return False
                        else:
                            return False

                # Make sure right doesn't have anything that left doesn't
                for k, v in right.items():
                    if v and k not in left:
                        return False

                return True

            def _task_definition_matches(
                requested_volumes, requested_containers, existing_task_definition
            ):
                if td["status"] != "ACTIVE":
                    return None

                existing_volumes = td.get("volumes", []) or []

                if len(requested_volumes) != len(existing_volumes):
                    # Nope.
                    return None

                if len(requested_volumes) > 0:
                    for requested_vol in requested_volumes:
                        found = False

                        for actual_vol in existing_volumes:
                            if _right_has_values_of_left(requested_vol, actual_vol):
                                found = True
                                break

                        if not found:
                            return None

                existing_containers = td.get("containerDefinitions", []) or []

                if len(requested_containers) != len(existing_containers):
                    # Nope.
                    return None

                for requested_container in requested_containers:
                    found = False

                    for actual_container in existing_containers:
                        if _right_has_values_of_left(
                            requested_container, actual_container
                        ):
                            found = True
                            break

                    if not found:
                        return None

                return existing_task_definition

            # No revision explicitly specified. Attempt to find an active, matching revision that has all the properties requested
            for td in existing_definitions_in_family:
                requested_volumes = module.params.get("volumes", []) or []
                requested_containers = module.params.get("containers", []) or []
                existing = _task_definition_matches(
                    requested_volumes, requested_containers, td
                )

                if existing:
                    break

        if existing:
            # Awesome. Have an existing one. Nothing to do.
            results["taskdefinition"] = existing
        else:
            if not module.check_mode:
                # Doesn't exist. create it.
                volumes = module.params.get("volumes", []) or []
                results["taskdefinition"] = task_mgr.register_task(
                    module.params["family"],
                    module.params["task_role_arn"],
                    module.params["network_mode"],
                    module.params["containers"],
                    volumes,
                )
            results["changed"] = True

    elif module.params["state"] == "absent":
        # When de-registering a task definition, we can specify the ARN OR the family and revision.
        if module.params["state"] == "absent":
            if "arn" in module.params and module.params["arn"] is not None:
                task_to_describe = module.params["arn"]
            elif (
                "family" in module.params
                and module.params["family"] is not None
                and "revision" in module.params
                and module.params["revision"] is not None
            ):
                task_to_describe = (
                    module.params["family"] + ":" + str(module.params["revision"])
                )
            else:
                module.fail_json(
                    msg="To use task definitions, an arn or family and revision must be specified"
                )

        existing = task_mgr.describe_task(task_to_describe)

        if not existing:
            pass
        else:
            # It exists, so we should delete it and mark changed. Return info about the task definition deleted
            results["taskdefinition"] = existing
            if "status" in existing and existing["status"] == "INACTIVE":
                results["changed"] = False
            else:
                if not module.check_mode:
                    task_mgr.deregister_task(task_to_describe)
                results["changed"] = True

    module.exit_json(**results)


# Traceback (TypeError)
An exception occurred during task execution. To see the full traceback, use -vvv. The error was: TypeError: 'NoneType' object is not iterable
fatal: [localhost]: FAILED! => {
"changed": false,
"failed": true,
"module_stderr": "Traceback (most recent call last):\n  File \"/tmp/ansible_UuUi_a/ansible_module_ecs_taskdefinition.py\", line 396, in <module>\n    main()\n  File \"/tmp/ansible_UuUi_a/ansible_module_ecs_taskdefinition.py\", line 244, in main\n    for container in module.params['containers']:\nTypeError: 'NoneType' object is not iterable\n",
"module_stdout": "",
"rc": 0
}

# Fixed code
def main():
    argument_spec = ec2_argument_spec()
    argument_spec.update(
        dict(
            state=dict(required=True, choices=["present", "absent"]),
            arn=dict(required=False, type="str"),
            family=dict(required=False, type="str"),
            revision=dict(required=False, type="int"),
            containers=dict(required=False, type="list"),
            network_mode=dict(
                required=False,
                default="bridge",
                choices=["bridge", "host", "none"],
                type="str",
            ),
            task_role_arn=dict(required=False, default="", type="str"),
            volumes=dict(required=False, type="list"),
        )
    )

    module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True)

    if not HAS_BOTO:
        module.fail_json(msg="boto is required.")

    if not HAS_BOTO3:
        module.fail_json(msg="boto3 is required.")

    task_to_describe = None
    task_mgr = EcsTaskManager(module)
    results = dict(changed=False)

    for container in module.params.get("containers", []):
        for environment in container.get("environment", []):
            environment["value"] = to_text(environment["value"])

    if module.params["state"] == "present":
        if "containers" not in module.params or not module.params["containers"]:
            module.fail_json(
                msg="To use task definitions, a list of containers must be specified"
            )

        if "family" not in module.params or not module.params["family"]:
            module.fail_json(msg="To use task definitions, a family must be specified")

        family = module.params["family"]
        existing_definitions_in_family = task_mgr.describe_task_definitions(
            module.params["family"]
        )

        if "revision" in module.params and module.params["revision"]:
            # The definition specifies revision. We must gurantee that an active revision of that number will result from this.
            revision = int(module.params["revision"])

            # A revision has been explicitly specified. Attempt to locate a matching revision
            tasks_defs_for_revision = [
                td
                for td in existing_definitions_in_family
                if td["revision"] == revision
            ]
            existing = (
                tasks_defs_for_revision[0] if len(tasks_defs_for_revision) > 0 else None
            )

            if existing and existing["status"] != "ACTIVE":
                # We cannot reactivate an inactive revision
                module.fail_json(
                    msg="A task in family '%s' already exists for revsion %d, but it is inactive"
                    % (family, revision)
                )
            elif not existing:
                if not existing_definitions_in_family and revision != 1:
                    module.fail_json(
                        msg="You have specified a revision of %d but a created revision would be 1"
                        % revision
                    )
                elif (
                    existing_definitions_in_family
                    and existing_definitions_in_family[-1]["revision"] + 1 != revision
                ):
                    module.fail_json(
                        msg="You have specified a revision of %d but a created revision would be %d"
                        % (revision, existing_definitions_in_family[-1]["revision"] + 1)
                    )
        else:
            existing = None

            def _right_has_values_of_left(left, right):
                # Make sure the values are equivalent for everything left has
                for k, v in left.items():
                    if not (
                        (not v and (k not in right or not right[k]))
                        or (k in right and v == right[k])
                    ):
                        # We don't care about list ordering because ECS can change things
                        if isinstance(v, list) and k in right:
                            left_list = v
                            right_list = right[k] or []

                            if len(left_list) != len(right_list):
                                return False

                            for list_val in left_list:
                                if list_val not in right_list:
                                    return False
                        else:
                            return False

                # Make sure right doesn't have anything that left doesn't
                for k, v in right.items():
                    if v and k not in left:
                        return False

                return True

            def _task_definition_matches(
                requested_volumes, requested_containers, existing_task_definition
            ):
                if td["status"] != "ACTIVE":
                    return None

                existing_volumes = td.get("volumes", []) or []

                if len(requested_volumes) != len(existing_volumes):
                    # Nope.
                    return None

                if len(requested_volumes) > 0:
                    for requested_vol in requested_volumes:
                        found = False

                        for actual_vol in existing_volumes:
                            if _right_has_values_of_left(requested_vol, actual_vol):
                                found = True
                                break

                        if not found:
                            return None

                existing_containers = td.get("containerDefinitions", []) or []

                if len(requested_containers) != len(existing_containers):
                    # Nope.
                    return None

                for requested_container in requested_containers:
                    found = False

                    for actual_container in existing_containers:
                        if _right_has_values_of_left(
                            requested_container, actual_container
                        ):
                            found = True
                            break

                    if not found:
                        return None

                return existing_task_definition

            # No revision explicitly specified. Attempt to find an active, matching revision that has all the properties requested
            for td in existing_definitions_in_family:
                requested_volumes = module.params.get("volumes", []) or []
                requested_containers = module.params.get("containers", []) or []
                existing = _task_definition_matches(
                    requested_volumes, requested_containers, td
                )

                if existing:
                    break

        if existing:
            # Awesome. Have an existing one. Nothing to do.
            results["taskdefinition"] = existing
        else:
            if not module.check_mode:
                # Doesn't exist. create it.
                volumes = module.params.get("volumes", []) or []
                results["taskdefinition"] = task_mgr.register_task(
                    module.params["family"],
                    module.params["task_role_arn"],
                    module.params["network_mode"],
                    module.params["containers"],
                    volumes,
                )
            results["changed"] = True

    elif module.params["state"] == "absent":
        # When de-registering a task definition, we can specify the ARN OR the family and revision.
        if module.params["state"] == "absent":
            if "arn" in module.params and module.params["arn"] is not None:
                task_to_describe = module.params["arn"]
            elif (
                "family" in module.params
                and module.params["family"] is not None
                and "revision" in module.params
                and module.params["revision"] is not None
            ):
                task_to_describe = (
                    module.params["family"] + ":" + str(module.params["revision"])
                )
            else:
                module.fail_json(
                    msg="To use task definitions, an arn or family and revision must be specified"
                )

        existing = task_mgr.describe_task(task_to_describe)

        if not existing:
            pass
        else:
            # It exists, so we should delete it and mark changed. Return info about the task definition deleted
            results["taskdefinition"] = existing
            if "status" in existing and existing["status"] == "INACTIVE":
                results["changed"] = False
            else:
                if not module.check_mode:
                    task_mgr.deregister_task(task_to_describe)
                results["changed"] = True

    module.exit_json(**results)