Repository: ansible-collections/amazon.aws

Issue:
[6.0.0] Drop support for EC2 Classic networking
### Summary

[Amazon announced that they're dropping support for "EC2 Classic" (non-VPC networking) in July 2021](https://aws.amazon.com/blogs/aws/ec2-classic-is-retiring-heres-how-to-prepare/).  This is sometimes referred to as "Classic-Link"

Specifically stating that:
> On August 15, 2022 [Amazon AWS] expect all migrations to be complete, with no remaining EC2-Classic resources present in any AWS account.

We're now past that date, it's reasonable to start dropping support for EC2-Classic for 6.0.0.  Anyone who's got an exception from Amazon to continue to support EC2 Classic will still be able to use our 5.x releases.

### Issue Type

Feature Idea

### Component Name

(Known examples)
plugins/modules/ec2_eip.py (`in_vpc` parameter - `Domain` when passed to boto3 should now always be `vpc`)
plugins/modules/ec2_vpc_net.py
plugins/modules/ec2_security_group.py
plugins/module_utils/botocore.py
plugins/lookup/aws_account_attribute.py  (see also #1092)

### Additional Information

**Note**: This does _NOT_ include Classic ELBs which _are_ still supported.

### Code of Conduct

- [X] I agree to follow the Ansible Code of Conduct


Discussion:

Resolving patch:
diff --git a/plugins/modules/elb_application_lb.py b/plugins/modules/elb_application_lb.py
new file mode 100644
--- /dev/null
+++ b/plugins/modules/elb_application_lb.py
@@ -0,0 +1,821 @@
+#!/usr/bin/python
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible 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 General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible.  If not, see <http://www.gnu.org/licenses/>.
+
+from __future__ import (absolute_import, division, print_function)
+__metaclass__ = type
+
+
+DOCUMENTATION = r'''
+---
+module: elb_application_lb
+version_added: 1.0.0
+short_description: Manage an Application Load Balancer
+description:
+  - Manage an AWS Application Elastic Load Balancer. See U(https://aws.amazon.com/blogs/aws/new-aws-application-load-balancer/) for details.
+author:
+  - "Rob White (@wimnat)"
+options:
+  access_logs_enabled:
+    description:
+      - Whether or not to enable access logs.
+      - When set, I(access_logs_s3_bucket) must also be set.
+    type: bool
+  access_logs_s3_bucket:
+    description:
+      - The name of the S3 bucket for the access logs.
+      - The bucket must exist in the same
+        region as the load balancer and have a bucket policy that grants Elastic Load Balancing permission to write to the bucket.
+      - Required if access logs in Amazon S3 are enabled.
+      - When set, I(access_logs_enabled) must also be set.
+    type: str
+  access_logs_s3_prefix:
+    description:
+      - The prefix for the log location in the S3 bucket.
+      - If you don't specify a prefix, the access logs are stored in the root of the bucket.
+      - Cannot begin or end with a slash.
+    type: str
+  deletion_protection:
+    description:
+      - Indicates whether deletion protection for the ALB is enabled.
+      - Defaults to C(False).
+    type: bool
+  http2:
+    description:
+      - Indicates whether to enable HTTP2 routing.
+      - Defaults to C(True).
+    type: bool
+  http_desync_mitigation_mode:
+    description:
+      - Determines how the load balancer handles requests that might pose a security risk to an application.
+      - Defaults to C('defensive')
+    type: str
+    choices: ['monitor', 'defensive', 'strictest']
+    version_added: 3.2.0
+  http_drop_invalid_header_fields:
+    description:
+      - Indicates whether HTTP headers with invalid header fields are removed by the load balancer C(True) or routed to targets C(False).
+      - Defaults to C(False).
+    type: bool
+    version_added: 3.2.0
+  http_x_amzn_tls_version_and_cipher_suite:
+    description:
+      - Indicates whether the two headers are added to the client request before sending it to the target.
+      - Defaults to C(False).
+    type: bool
+    version_added: 3.2.0
+  http_xff_client_port:
+    description:
+      - Indicates whether the X-Forwarded-For header should preserve the source port that the client used to connect to the load balancer.
+      - Defaults to C(False).
+    type: bool
+    version_added: 3.2.0
+  idle_timeout:
+    description:
+      - The number of seconds to wait before an idle connection is closed.
+    type: int
+  listeners:
+    description:
+      - A list of dicts containing listeners to attach to the ALB. See examples for detail of the dict required. Note that listener keys
+        are CamelCased.
+    type: list
+    elements: dict
+    suboptions:
+        Port:
+            description: The port on which the load balancer is listening.
+            required: true
+            type: int
+        Protocol:
+            description: The protocol for connections from clients to the load balancer.
+            required: true
+            type: str
+        Certificates:
+            description: The SSL server certificate.
+            type: list
+            elements: dict
+            suboptions:
+                CertificateArn:
+                    description: The Amazon Resource Name (ARN) of the certificate.
+                    type: str
+        SslPolicy:
+            description: The security policy that defines which ciphers and protocols are supported.
+            type: str
+        DefaultActions:
+            description: The default actions for the listener.
+            required: true
+            type: list
+            elements: dict
+            suboptions:
+                Type:
+                    description: The type of action.
+                    type: str
+                TargetGroupArn:
+                    description:
+                      - The Amazon Resource Name (ARN) of the target group.
+                      - Mutually exclusive with I(TargetGroupName).
+                    type: str
+                TargetGroupName:
+                    description:
+                      - The name of the target group.
+                      - Mutually exclusive with I(TargetGroupArn).
+        Rules:
+            type: list
+            elements: dict
+            description:
+              - A list of ALB Listener Rules.
+              - 'For the complete documentation of possible Conditions and Actions please see the boto3 documentation:'
+              - 'https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/elbv2.html#ElasticLoadBalancingv2.Client.create_rule'
+            suboptions:
+                Conditions:
+                    type: list
+                    description: Conditions which must be met for the actions to be applied.
+                    elements: dict
+                Priority:
+                    type: int
+                    description: The rule priority.
+                Actions:
+                    type: list
+                    description: Actions to apply if all of the rule's conditions are met.
+                    elements: dict
+  name:
+    description:
+      - The name of the load balancer. This name must be unique within your AWS account, can have a maximum of 32 characters, must contain only alphanumeric
+        characters or hyphens, and must not begin or end with a hyphen.
+    required: true
+    type: str
+  purge_listeners:
+    description:
+      - If C(true), existing listeners will be purged from the ALB to match exactly what is defined by I(listeners) parameter.
+      - If the I(listeners) parameter is not set then listeners will not be modified.
+    default: true
+    type: bool
+  subnets:
+    description:
+      - A list of the IDs of the subnets to attach to the load balancer. You can specify only one subnet per Availability Zone. You must specify subnets from
+        at least two Availability Zones.
+      - Required if I(state=present).
+    type: list
+    elements: str
+  security_groups:
+    description:
+      - A list of the names or IDs of the security groups to assign to the load balancer.
+      - Required if I(state=present).
+      - If C([]), the VPC's default security group will be used.
+    type: list
+    elements: str
+  scheme:
+    description:
+      - Internet-facing or internal load balancer. An ALB scheme can not be modified after creation.
+    default: internet-facing
+    choices: [ 'internet-facing', 'internal' ]
+    type: str
+  state:
+    description:
+      - Create or destroy the load balancer.
+    default: present
+    choices: [ 'present', 'absent' ]
+    type: str
+  wait:
+    description:
+      - Wait for the load balancer to have a state of 'active' before completing. A status check is
+        performed every 15 seconds until a successful state is reached. An error is returned after 40 failed checks.
+    default: false
+    type: bool
+  wait_timeout:
+    description:
+      - The time in seconds to use in conjunction with I(wait).
+    type: int
+  purge_rules:
+    description:
+      - When set to C(no), keep the existing load balancer rules in place. Will modify and add, but will not delete.
+    default: true
+    type: bool
+  ip_address_type:
+    description:
+      - Sets the type of IP addresses used by the subnets of the specified Application Load Balancer.
+    choices: [ 'ipv4', 'dualstack' ]
+    type: str
+  waf_fail_open:
+    description:
+      - Indicates whether to allow a AWS WAF-enabled load balancer to route requests to targets if it is unable to forward the request to AWS WAF.
+      - Defaults to C(False).
+    type: bool
+    version_added: 3.2.0
+extends_documentation_fragment:
+  - amazon.aws.aws
+  - amazon.aws.ec2
+  - amazon.aws.tags
+
+notes:
+  - Listeners are matched based on port. If a listener's port is changed then a new listener will be created.
+  - Listener rules are matched based on priority. If a rule's priority is changed then a new rule will be created.
+'''
+
+EXAMPLES = r'''
+# Note: These examples do not set authentication details, see the AWS Guide for details.
+
+# Create an ALB and attach a listener
+- amazon.aws.elb_application_lb:
+    name: myalb
+    security_groups:
+      - sg-12345678
+      - my-sec-group
+    subnets:
+      - subnet-012345678
+      - subnet-abcdef000
+    listeners:
+      - Protocol: HTTP # Required. The protocol for connections from clients to the load balancer (HTTP or HTTPS) (case-sensitive).
+        Port: 80 # Required. The port on which the load balancer is listening.
+        # The security policy that defines which ciphers and protocols are supported. The default is the current predefined security policy.
+        SslPolicy: ELBSecurityPolicy-2015-05
+        Certificates: # The ARN of the certificate (only one certficate ARN should be provided)
+          - CertificateArn: arn:aws:iam::12345678987:server-certificate/test.domain.com
+        DefaultActions:
+          - Type: forward # Required.
+            TargetGroupName: # Required. The name of the target group
+    state: present
+
+# Create an ALB and attach a listener with logging enabled
+- amazon.aws.elb_application_lb:
+    access_logs_enabled: true
+    access_logs_s3_bucket: mybucket
+    access_logs_s3_prefix: "logs"
+    name: myalb
+    security_groups:
+      - sg-12345678
+      - my-sec-group
+    subnets:
+      - subnet-012345678
+      - subnet-abcdef000
+    listeners:
+      - Protocol: HTTP # Required. The protocol for connections from clients to the load balancer (HTTP or HTTPS) (case-sensitive).
+        Port: 80 # Required. The port on which the load balancer is listening.
+        # The security policy that defines which ciphers and protocols are supported. The default is the current predefined security policy.
+        SslPolicy: ELBSecurityPolicy-2015-05
+        Certificates: # The ARN of the certificate (only one certficate ARN should be provided)
+          - CertificateArn: arn:aws:iam::12345678987:server-certificate/test.domain.com
+        DefaultActions:
+          - Type: forward # Required.
+            TargetGroupName: # Required. The name of the target group
+    state: present
+
+# Create an ALB with listeners and rules
+- amazon.aws.elb_application_lb:
+    name: test-alb
+    subnets:
+      - subnet-12345678
+      - subnet-87654321
+    security_groups:
+      - sg-12345678
+    scheme: internal
+    listeners:
+      - Protocol: HTTPS
+        Port: 443
+        DefaultActions:
+          - Type: forward
+            TargetGroupName: test-target-group
+        Certificates:
+          - CertificateArn: arn:aws:iam::12345678987:server-certificate/test.domain.com
+        SslPolicy: ELBSecurityPolicy-2015-05
+        Rules:
+          - Conditions:
+              - Field: path-pattern
+                Values:
+                  - '/test'
+            Priority: '1'
+            Actions:
+              - TargetGroupName: test-target-group
+                Type: forward
+          - Conditions:
+              - Field: path-pattern
+                Values:
+                  - "/redirect-path/*"
+            Priority: '2'
+            Actions:
+              - Type: redirect
+                RedirectConfig:
+                  Host: "#{host}"
+                  Path: "/example/redir" # or /#{path}
+                  Port: "#{port}"
+                  Protocol: "#{protocol}"
+                  Query: "#{query}"
+                  StatusCode: "HTTP_302" # or HTTP_301
+          - Conditions:
+              - Field: path-pattern
+                Values:
+                  - "/fixed-response-path/"
+            Priority: '3'
+            Actions:
+              - Type: fixed-response
+                FixedResponseConfig:
+                  ContentType: "text/plain"
+                  MessageBody: "This is the page you're looking for"
+                  StatusCode: "200"
+          - Conditions:
+              - Field: host-header
+                Values:
+                  - "hostname.domain.com"
+                  - "alternate.domain.com"
+            Priority: '4'
+            Actions:
+              - TargetGroupName: test-target-group
+                Type: forward
+    state: present
+
+# Remove an ALB
+- amazon.aws.elb_application_lb:
+    name: myalb
+    state: absent
+
+'''
+
+RETURN = r'''
+access_logs_s3_bucket:
+    description: The name of the S3 bucket for the access logs.
+    returned: when state is present
+    type: str
+    sample: "mys3bucket"
+access_logs_s3_enabled:
+    description: Indicates whether access logs stored in Amazon S3 are enabled.
+    returned: when state is present
+    type: bool
+    sample: true
+access_logs_s3_prefix:
+    description: The prefix for the location in the S3 bucket.
+    returned: when state is present
+    type: str
+    sample: "my/logs"
+availability_zones:
+    description: The Availability Zones for the load balancer.
+    returned: when state is present
+    type: list
+    sample: [{ "load_balancer_addresses": [], "subnet_id": "subnet-aabbccddff", "zone_name": "ap-southeast-2a" }]
+canonical_hosted_zone_id:
+    description: The ID of the Amazon Route 53 hosted zone associated with the load balancer.
+    returned: when state is present
+    type: str
+    sample: "ABCDEF12345678"
+changed:
+    description: Whether an ALB was created/updated/deleted
+    returned: always
+    type: bool
+    sample: true
+created_time:
+    description: The date and time the load balancer was created.
+    returned: when state is present
+    type: str
+    sample: "2015-02-12T02:14:02+00:00"
+deletion_protection_enabled:
+    description: Indicates whether deletion protection is enabled.
+    returned: when state is present
+    type: bool
+    sample: true
+dns_name:
+    description: The public DNS name of the load balancer.
+    returned: when state is present
+    type: str
+    sample: "internal-my-elb-123456789.ap-southeast-2.elb.amazonaws.com"
+idle_timeout_timeout_seconds:
+    description: The idle timeout value, in seconds.
+    returned: when state is present
+    type: int
+    sample: 60
+ip_address_type:
+    description: The type of IP addresses used by the subnets for the load balancer.
+    returned: when state is present
+    type: str
+    sample: "ipv4"
+listeners:
+    description: Information about the listeners.
+    returned: when state is present
+    type: complex
+    contains:
+        listener_arn:
+            description: The Amazon Resource Name (ARN) of the listener.
+            returned: when state is present
+            type: str
+            sample: ""
+        load_balancer_arn:
+            description: The Amazon Resource Name (ARN) of the load balancer.
+            returned: when state is present
+            type: str
+            sample: ""
+        port:
+            description: The port on which the load balancer is listening.
+            returned: when state is present
+            type: int
+            sample: 80
+        protocol:
+            description: The protocol for connections from clients to the load balancer.
+            returned: when state is present
+            type: str
+            sample: "HTTPS"
+        certificates:
+            description: The SSL server certificate.
+            returned: when state is present
+            type: complex
+            contains:
+                certificate_arn:
+                    description: The Amazon Resource Name (ARN) of the certificate.
+                    returned: when state is present
+                    type: str
+                    sample: ""
+        ssl_policy:
+            description: The security policy that defines which ciphers and protocols are supported.
+            returned: when state is present
+            type: str
+            sample: ""
+        default_actions:
+            description: The default actions for the listener.
+            returned: when state is present
+            type: str
+            contains:
+                type:
+                    description: The type of action.
+                    returned: when state is present
+                    type: str
+                    sample: ""
+                target_group_arn:
+                    description: The Amazon Resource Name (ARN) of the target group.
+                    returned: when state is present
+                    type: str
+                    sample: ""
+load_balancer_arn:
+    description: The Amazon Resource Name (ARN) of the load balancer.
+    returned: when state is present
+    type: str
+    sample: "arn:aws:elasticloadbalancing:ap-southeast-2:0123456789:loadbalancer/app/my-alb/001122334455"
+load_balancer_name:
+    description: The name of the load balancer.
+    returned: when state is present
+    type: str
+    sample: "my-alb"
+routing_http2_enabled:
+    description: Indicates whether HTTP/2 is enabled.
+    returned: when state is present
+    type: bool
+    sample: true
+routing_http_desync_mitigation_mode:
+    description: Determines how the load balancer handles requests that might pose a security risk to an application.
+    returned: when state is present
+    type: str
+    sample: "defensive"
+routing_http_drop_invalid_header_fields_enabled:
+    description: Indicates whether HTTP headers with invalid header fields are removed by the load balancer (true) or routed to targets (false).
+    returned: when state is present
+    type: bool
+    sample: false
+routing_http_x_amzn_tls_version_and_cipher_suite_enabled:
+    description: Indicates whether the two headers are added to the client request before sending it to the target.
+    returned: when state is present
+    type: bool
+    sample: false
+routing_http_xff_client_port_enabled:
+    description: Indicates whether the X-Forwarded-For header should preserve the source port that the client used to connect to the load balancer.
+    returned: when state is present
+    type: bool
+    sample: false
+scheme:
+    description: Internet-facing or internal load balancer.
+    returned: when state is present
+    type: str
+    sample: "internal"
+security_groups:
+    description: The IDs of the security groups for the load balancer.
+    returned: when state is present
+    type: list
+    sample: ['sg-0011223344']
+state:
+    description: The state of the load balancer.
+    returned: when state is present
+    type: dict
+    sample: {'code': 'active'}
+tags:
+    description: The tags attached to the load balancer.
+    returned: when state is present
+    type: dict
+    sample: {
+        'Tag': 'Example'
+    }
+type:
+    description: The type of load balancer.
+    returned: when state is present
+    type: str
+    sample: "application"
+vpc_id:
+    description: The ID of the VPC for the load balancer.
+    returned: when state is present
+    type: str
+    sample: "vpc-0011223344"
+waf_fail_open_enabled:
+    description: Indicates whether to allow a AWS WAF-enabled load balancer to route requests to targets if it is unable to forward the request to AWS WAF.
+    returned: when state is present
+    type: bool
+    sample: false
+'''
+try:
+    import botocore
+except ImportError:
+    pass  # caught by AnsibleAWSModule
+
+from ansible_collections.amazon.aws.plugins.module_utils.core import AnsibleAWSModule
+from ansible_collections.amazon.aws.plugins.module_utils.ec2 import ansible_dict_to_boto3_filter_list
+from ansible_collections.amazon.aws.plugins.module_utils.ec2 import AWSRetry
+from ansible_collections.amazon.aws.plugins.module_utils.ec2 import boto3_tag_list_to_ansible_dict
+from ansible_collections.amazon.aws.plugins.module_utils.ec2 import camel_dict_to_snake_dict
+from ansible_collections.amazon.aws.plugins.module_utils.ec2 import compare_aws_tags
+from ansible_collections.amazon.aws.plugins.module_utils.elbv2 import (
+    ApplicationLoadBalancer,
+    ELBListener,
+    ELBListenerRule,
+    ELBListenerRules,
+    ELBListeners,
+)
+from ansible_collections.amazon.aws.plugins.module_utils.elb_utils import get_elb_listener_rules
+
+
+@AWSRetry.jittered_backoff()
+def describe_sgs_with_backoff(connection, **params):
+    paginator = connection.get_paginator('describe_security_groups')
+    return paginator.paginate(**params).build_full_result()['SecurityGroups']
+
+
+def find_default_sg(connection, module, vpc_id):
+    """
+    Finds the default security group for the given VPC ID.
+    """
+    filters = ansible_dict_to_boto3_filter_list({'vpc-id': vpc_id, 'group-name': 'default'})
+    try:
+        sg = describe_sgs_with_backoff(connection, Filters=filters)
+    except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
+        module.fail_json_aws(e, msg='No default security group found for VPC {0}'.format(vpc_id))
+    if len(sg) == 1:
+        return sg[0]['GroupId']
+    elif len(sg) == 0:
+        module.fail_json(msg='No default security group found for VPC {0}'.format(vpc_id))
+    else:
+        module.fail_json(msg='Multiple security groups named "default" found for VPC {0}'.format(vpc_id))
+
+
+def create_or_update_alb(alb_obj):
+    """Create ALB or modify main attributes. json_exit here"""
+    if alb_obj.elb:
+        # ALB exists so check subnets, security groups and tags match what has been passed
+        # Subnets
+        if not alb_obj.compare_subnets():
+            if alb_obj.module.check_mode:
+                alb_obj.module.exit_json(changed=True, msg='Would have updated ALB if not in check mode.')
+            alb_obj.modify_subnets()
+
+        # Security Groups
+        if not alb_obj.compare_security_groups():
+            if alb_obj.module.check_mode:
+                alb_obj.module.exit_json(changed=True, msg='Would have updated ALB if not in check mode.')
+            alb_obj.modify_security_groups()
+
+        # ALB attributes
+        if not alb_obj.compare_elb_attributes():
+            if alb_obj.module.check_mode:
+                alb_obj.module.exit_json(changed=True, msg='Would have updated ALB if not in check mode.')
+            alb_obj.update_elb_attributes()
+            alb_obj.modify_elb_attributes()
+
+        # Tags - only need to play with tags if tags parameter has been set to something
+        if alb_obj.tags is not None:
+
+            tags_need_modify, tags_to_delete = compare_aws_tags(boto3_tag_list_to_ansible_dict(alb_obj.elb['tags']),
+                                                                boto3_tag_list_to_ansible_dict(alb_obj.tags), alb_obj.purge_tags)
+
+            # Exit on check_mode
+            if alb_obj.module.check_mode and (tags_need_modify or tags_to_delete):
+                alb_obj.module.exit_json(changed=True, msg='Would have updated ALB if not in check mode.')
+
+            # Delete necessary tags
+            if tags_to_delete:
+                alb_obj.delete_tags(tags_to_delete)
+
+            # Add/update tags
+            if tags_need_modify:
+                alb_obj.modify_tags()
+
+    else:
+        # Create load balancer
+        if alb_obj.module.check_mode:
+            alb_obj.module.exit_json(changed=True, msg='Would have created ALB if not in check mode.')
+        alb_obj.create_elb()
+
+    # Listeners
+    listeners_obj = ELBListeners(alb_obj.connection, alb_obj.module, alb_obj.elb['LoadBalancerArn'])
+    listeners_to_add, listeners_to_modify, listeners_to_delete = listeners_obj.compare_listeners()
+
+    # Exit on check_mode
+    if alb_obj.module.check_mode and (listeners_to_add or listeners_to_modify or listeners_to_delete):
+        alb_obj.module.exit_json(changed=True, msg='Would have updated ALB if not in check mode.')
+
+    # Delete listeners
+    for listener_to_delete in listeners_to_delete:
+        listener_obj = ELBListener(alb_obj.connection, alb_obj.module, listener_to_delete, alb_obj.elb['LoadBalancerArn'])
+        listener_obj.delete()
+        listeners_obj.changed = True
+
+    # Add listeners
+    for listener_to_add in listeners_to_add:
+        listener_obj = ELBListener(alb_obj.connection, alb_obj.module, listener_to_add, alb_obj.elb['LoadBalancerArn'])
+        listener_obj.add()
+        listeners_obj.changed = True
+
+    # Modify listeners
+    for listener_to_modify in listeners_to_modify:
+        listener_obj = ELBListener(alb_obj.connection, alb_obj.module, listener_to_modify, alb_obj.elb['LoadBalancerArn'])
+        listener_obj.modify()
+        listeners_obj.changed = True
+
+    # If listeners changed, mark ALB as changed
+    if listeners_obj.changed:
+        alb_obj.changed = True
+
+    # Rules of each listener
+    for listener in listeners_obj.listeners:
+        if 'Rules' in listener:
+            rules_obj = ELBListenerRules(alb_obj.connection, alb_obj.module, alb_obj.elb['LoadBalancerArn'], listener['Rules'], listener['Port'])
+            rules_to_add, rules_to_modify, rules_to_delete = rules_obj.compare_rules()
+
+            # Exit on check_mode
+            if alb_obj.module.check_mode and (rules_to_add or rules_to_modify or rules_to_delete):
+                alb_obj.module.exit_json(changed=True, msg='Would have updated ALB if not in check mode.')
+
+            # Delete rules
+            if alb_obj.module.params['purge_rules']:
+                for rule in rules_to_delete:
+                    rule_obj = ELBListenerRule(alb_obj.connection, alb_obj.module, {'RuleArn': rule}, rules_obj.listener_arn)
+                    rule_obj.delete()
+                    alb_obj.changed = True
+
+            # Add rules
+            for rule in rules_to_add:
+                rule_obj = ELBListenerRule(alb_obj.connection, alb_obj.module, rule, rules_obj.listener_arn)
+                rule_obj.create()
+                alb_obj.changed = True
+
+            # Modify rules
+            for rule in rules_to_modify:
+                rule_obj = ELBListenerRule(alb_obj.connection, alb_obj.module, rule, rules_obj.listener_arn)
+                rule_obj.modify()
+                alb_obj.changed = True
+
+    # Update ALB ip address type only if option has been provided
+    if alb_obj.module.params.get('ip_address_type') and alb_obj.elb_ip_addr_type != alb_obj.module.params.get('ip_address_type'):
+        # Exit on check_mode
+        if alb_obj.module.check_mode:
+            alb_obj.module.exit_json(changed=True, msg='Would have updated ALB if not in check mode.')
+
+        alb_obj.modify_ip_address_type(alb_obj.module.params.get('ip_address_type'))
+
+    # Exit on check_mode - no changes
+    if alb_obj.module.check_mode:
+        alb_obj.module.exit_json(changed=False, msg='IN CHECK MODE - no changes to make to ALB specified.')
+
+    # Get the ALB again
+    alb_obj.update()
+
+    # Get the ALB listeners again
+    listeners_obj.update()
+
+    # Update the ALB attributes
+    alb_obj.update_elb_attributes()
+
+    # Convert to snake_case and merge in everything we want to return to the user
+    snaked_alb = camel_dict_to_snake_dict(alb_obj.elb)
+    snaked_alb.update(camel_dict_to_snake_dict(alb_obj.elb_attributes))
+    snaked_alb['listeners'] = []
+    for listener in listeners_obj.current_listeners:
+        # For each listener, get listener rules
+        listener['rules'] = get_elb_listener_rules(alb_obj.connection, alb_obj.module, listener['ListenerArn'])
+        snaked_alb['listeners'].append(camel_dict_to_snake_dict(listener))
+
+    # Change tags to ansible friendly dict
+    snaked_alb['tags'] = boto3_tag_list_to_ansible_dict(snaked_alb['tags'])
+
+    # ip address type
+    snaked_alb['ip_address_type'] = alb_obj.get_elb_ip_address_type()
+
+    alb_obj.module.exit_json(changed=alb_obj.changed, **snaked_alb)
+
+
+def delete_alb(alb_obj):
+
+    if alb_obj.elb:
+
+        # Exit on check_mode
+        if alb_obj.module.check_mode:
+            alb_obj.module.exit_json(changed=True, msg='Would have deleted ALB if not in check mode.')
+
+        listeners_obj = ELBListeners(alb_obj.connection, alb_obj.module, alb_obj.elb['LoadBalancerArn'])
+        for listener_to_delete in [i['ListenerArn'] for i in listeners_obj.current_listeners]:
+            listener_obj = ELBListener(alb_obj.connection, alb_obj.module, listener_to_delete, alb_obj.elb['LoadBalancerArn'])
+            listener_obj.delete()
+
+        alb_obj.delete()
+
+    else:
+
+        # Exit on check_mode - no changes
+        if alb_obj.module.check_mode:
+            alb_obj.module.exit_json(changed=False, msg='IN CHECK MODE - ALB already absent.')
+
+    alb_obj.module.exit_json(changed=alb_obj.changed)
+
+
+def main():
+
+    argument_spec = dict(
+        access_logs_enabled=dict(type='bool'),
+        access_logs_s3_bucket=dict(type='str'),
+        access_logs_s3_prefix=dict(type='str'),
+        deletion_protection=dict(type='bool'),
+        http2=dict(type='bool'),
+        http_desync_mitigation_mode=dict(type='str', choices=['monitor', 'defensive', 'strictest']),
+        http_drop_invalid_header_fields=dict(type='bool'),
+        http_x_amzn_tls_version_and_cipher_suite=dict(type='bool'),
+        http_xff_client_port=dict(type='bool'),
+        idle_timeout=dict(type='int'),
+        listeners=dict(type='list',
+                       elements='dict',
+                       options=dict(
+                           Protocol=dict(type='str', required=True),
+                           Port=dict(type='int', required=True),
+                           SslPolicy=dict(type='str'),
+                           Certificates=dict(type='list', elements='dict'),
+                           DefaultActions=dict(type='list', required=True, elements='dict'),
+                           Rules=dict(type='list', elements='dict')
+                       )
+                       ),
+        name=dict(required=True, type='str'),
+        purge_listeners=dict(default=True, type='bool'),
+        purge_tags=dict(default=True, type='bool'),
+        subnets=dict(type='list', elements='str'),
+        security_groups=dict(type='list', elements='str'),
+        scheme=dict(default='internet-facing', choices=['internet-facing', 'internal']),
+        state=dict(choices=['present', 'absent'], default='present'),
+        tags=dict(type='dict', aliases=['resource_tags']),
+        waf_fail_open=dict(type='bool'),
+        wait_timeout=dict(type='int'),
+        wait=dict(default=False, type='bool'),
+        purge_rules=dict(default=True, type='bool'),
+        ip_address_type=dict(type='str', choices=['ipv4', 'dualstack'])
+    )
+
+    module = AnsibleAWSModule(argument_spec=argument_spec,
+                              required_if=[
+                                  ('state', 'present', ['subnets', 'security_groups'])
+                              ],
+                              required_together=[
+                                  ['access_logs_enabled', 'access_logs_s3_bucket']
+                              ],
+                              supports_check_mode=True,
+                              )
+
+    # Quick check of listeners parameters
+    listeners = module.params.get("listeners")
+    if listeners is not None:
+        for listener in listeners:
+            for key in listener.keys():
+                if key == 'Protocol' and listener[key] == 'HTTPS':
+                    if listener.get('SslPolicy') is None:
+                        module.fail_json(msg="'SslPolicy' is a required listener dict key when Protocol = HTTPS")
+
+                    if listener.get('Certificates') is None:
+                        module.fail_json(msg="'Certificates' is a required listener dict key when Protocol = HTTPS")
+
+    connection = module.client('elbv2')
+    connection_ec2 = module.client('ec2')
+
+    state = module.params.get("state")
+
+    alb = ApplicationLoadBalancer(connection, connection_ec2, module)
+
+    # Update security group if default is specified
+    if alb.elb and module.params.get('security_groups') == []:
+        module.params['security_groups'] = [find_default_sg(connection_ec2, module, alb.elb['VpcId'])]
+        alb = ApplicationLoadBalancer(connection, connection_ec2, module)
+
+    if state == 'present':
+        create_or_update_alb(alb)
+    elif state == 'absent':
+        delete_alb(alb)
+
+
+if __name__ == '__main__':
+    main()
diff --git a/plugins/modules/elb_application_lb_info.py b/plugins/modules/elb_application_lb_info.py
new file mode 100644
--- /dev/null
+++ b/plugins/modules/elb_application_lb_info.py
@@ -0,0 +1,340 @@
+#!/usr/bin/python
+# Copyright: Ansible Project
+# 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
+
+
+DOCUMENTATION = r'''
+---
+module: elb_application_lb_info
+version_added: 1.0.0
+short_description: Gather information about Application Load Balancers in AWS
+description:
+    - Gather information about Application Load Balancers in AWS
+author: Rob White (@wimnat)
+options:
+  load_balancer_arns:
+    description:
+      - The Amazon Resource Names (ARN) of the load balancers. You can specify up to 20 load balancers in a single call.
+    required: false
+    type: list
+    elements: str
+  names:
+    description:
+      - The names of the load balancers.
+    required: false
+    type: list
+    elements: str
+
+extends_documentation_fragment:
+- amazon.aws.aws
+- amazon.aws.ec2
+
+'''
+
+EXAMPLES = r'''
+# Note: These examples do not set authentication details, see the AWS Guide for details.
+
+- name: Gather information about all ALBs
+  amazon.aws.elb_application_lb_info:
+
+- name: Gather information about a particular ALB given its ARN
+  amazon.aws.elb_application_lb_info:
+    load_balancer_arns:
+      - "arn:aws:elasticloadbalancing:ap-southeast-2:001122334455:loadbalancer/app/my-alb/aabbccddeeff"
+
+- name: Gather information about ALBs named 'alb1' and 'alb2'
+  amazon.aws.elb_application_lb_info:
+    names:
+      - alb1
+      - alb2
+
+- name: Gather information about specific ALB
+  amazon.aws.elb_application_lb_info:
+    names: "alb-name"
+    region: "aws-region"
+  register: alb_info
+- ansible.builtin.debug:
+    var: alb_info
+'''
+
+RETURN = r'''
+load_balancers:
+    description: a list of load balancers
+    returned: always
+    type: complex
+    contains:
+        access_logs_s3_bucket:
+            description: The name of the S3 bucket for the access logs.
+            type: str
+            sample: "mys3bucket"
+        access_logs_s3_enabled:
+            description: Indicates whether access logs stored in Amazon S3 are enabled.
+            type: bool
+            sample: true
+        access_logs_s3_prefix:
+            description: The prefix for the location in the S3 bucket.
+            type: str
+            sample: "my/logs"
+        availability_zones:
+            description: The Availability Zones for the load balancer.
+            type: list
+            sample: [{ "load_balancer_addresses": [], "subnet_id": "subnet-aabbccddff", "zone_name": "ap-southeast-2a" }]
+        canonical_hosted_zone_id:
+            description: The ID of the Amazon Route 53 hosted zone associated with the load balancer.
+            type: str
+            sample: "ABCDEF12345678"
+        created_time:
+            description: The date and time the load balancer was created.
+            type: str
+            sample: "2015-02-12T02:14:02+00:00"
+        deletion_protection_enabled:
+            description: Indicates whether deletion protection is enabled.
+            type: bool
+            sample: true
+        dns_name:
+            description: The public DNS name of the load balancer.
+            type: str
+            sample: "internal-my-alb-123456789.ap-southeast-2.elb.amazonaws.com"
+        idle_timeout_timeout_seconds:
+            description: The idle timeout value, in seconds.
+            type: int
+            sample: 60
+        ip_address_type:
+            description: The type of IP addresses used by the subnets for the load balancer.
+            type: str
+            sample: "ipv4"
+        listeners:
+            description: Information about the listeners.
+            type: complex
+            contains:
+                listener_arn:
+                    description: The Amazon Resource Name (ARN) of the listener.
+                    type: str
+                    sample: ""
+                load_balancer_arn:
+                    description: The Amazon Resource Name (ARN) of the load balancer.
+                    type: str
+                    sample: ""
+                port:
+                    description: The port on which the load balancer is listening.
+                    type: int
+                    sample: 80
+                protocol:
+                    description: The protocol for connections from clients to the load balancer.
+                    type: str
+                    sample: "HTTPS"
+                certificates:
+                    description: The SSL server certificate.
+                    type: complex
+                    contains:
+                        certificate_arn:
+                            description: The Amazon Resource Name (ARN) of the certificate.
+                            type: str
+                            sample: ""
+                ssl_policy:
+                    description: The security policy that defines which ciphers and protocols are supported.
+                    type: str
+                    sample: ""
+                default_actions:
+                    description: The default actions for the listener.
+                    type: str
+                    contains:
+                        type:
+                            description: The type of action.
+                            type: str
+                            sample: ""
+                        target_group_arn:
+                            description: The Amazon Resource Name (ARN) of the target group.
+                            type: str
+                            sample: ""
+        load_balancer_arn:
+            description: The Amazon Resource Name (ARN) of the load balancer.
+            type: str
+            sample: "arn:aws:elasticloadbalancing:ap-southeast-2:0123456789:loadbalancer/app/my-alb/001122334455"
+        load_balancer_name:
+            description: The name of the load balancer.
+            type: str
+            sample: "my-alb"
+        routing_http2_enabled:
+            description: Indicates whether HTTP/2 is enabled.
+            type: bool
+            sample: true
+        routing_http_desync_mitigation_mode:
+            description: Determines how the load balancer handles requests that might pose a security risk to an application.
+            type: str
+            sample: "defensive"
+        routing_http_drop_invalid_header_fields_enabled:
+            description: Indicates whether HTTP headers with invalid header fields are removed by the load balancer (true) or routed to targets (false).
+            type: bool
+            sample: false
+        routing_http_x_amzn_tls_version_and_cipher_suite_enabled:
+            description: Indicates whether the two headers are added to the client request before sending it to the target.
+            type: bool
+            sample: false
+        routing_http_xff_client_port_enabled:
+            description: Indicates whether the X-Forwarded-For header should preserve the source port that the client used to connect to the load balancer.
+            type: bool
+            sample: false
+        scheme:
+            description: Internet-facing or internal load balancer.
+            type: str
+            sample: "internal"
+        security_groups:
+            description: The IDs of the security groups for the load balancer.
+            type: list
+            sample: ['sg-0011223344']
+        state:
+            description: The state of the load balancer.
+            type: dict
+            sample: {'code': 'active'}
+        tags:
+            description: The tags attached to the load balancer.
+            type: dict
+            sample: {
+                'Tag': 'Example'
+            }
+        type:
+            description: The type of load balancer.
+            type: str
+            sample: "application"
+        vpc_id:
+            description: The ID of the VPC for the load balancer.
+            type: str
+            sample: "vpc-0011223344"
+        waf_fail_open_enabled:
+            description: Indicates whether to allow a AWS WAF-enabled load balancer to route requests to targets
+                if it is unable to forward the request to AWS WAF.
+            type: bool
+            sample: false
+'''
+
+try:
+    import botocore
+except ImportError:
+    pass  # Handled by AnsibleAWSModule
+
+from ansible.module_utils.common.dict_transformations import camel_dict_to_snake_dict
+
+from ansible_collections.amazon.aws.plugins.module_utils.core import AnsibleAWSModule
+from ansible_collections.amazon.aws.plugins.module_utils.core import is_boto3_error_code
+from ansible_collections.amazon.aws.plugins.module_utils.ec2 import AWSRetry, boto3_tag_list_to_ansible_dict
+
+
+@AWSRetry.jittered_backoff(retries=10)
+def get_paginator(connection, **kwargs):
+    paginator = connection.get_paginator('describe_load_balancers')
+    return paginator.paginate(**kwargs).build_full_result()
+
+
+def get_alb_listeners(connection, module, alb_arn):
+
+    try:
+        return connection.describe_listeners(LoadBalancerArn=alb_arn)['Listeners']
+    except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
+        module.fail_json_aws(e, msg="Failed to describe alb listeners")
+
+
+def get_listener_rules(connection, module, listener_arn):
+
+    try:
+        return connection.describe_rules(ListenerArn=listener_arn)['Rules']
+    except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
+        module.fail_json_aws(e, msg="Failed to describe listener rules")
+
+
+def get_load_balancer_attributes(connection, module, load_balancer_arn):
+
+    try:
+        load_balancer_attributes = boto3_tag_list_to_ansible_dict(connection.describe_load_balancer_attributes(LoadBalancerArn=load_balancer_arn)['Attributes'])
+    except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
+        module.fail_json_aws(e, msg="Failed to describe load balancer attributes")
+
+    # Replace '.' with '_' in attribute key names to make it more Ansibley
+    for k, v in list(load_balancer_attributes.items()):
+        load_balancer_attributes[k.replace('.', '_')] = v
+        del load_balancer_attributes[k]
+
+    return load_balancer_attributes
+
+
+def get_load_balancer_tags(connection, module, load_balancer_arn):
+
+    try:
+        return boto3_tag_list_to_ansible_dict(connection.describe_tags(ResourceArns=[load_balancer_arn])['TagDescriptions'][0]['Tags'])
+    except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
+        module.fail_json_aws(e, msg="Failed to describe load balancer tags")
+
+
+def get_load_balancer_ipaddresstype(connection, module, load_balancer_arn):
+    try:
+        return connection.describe_load_balancers(LoadBalancerArns=[load_balancer_arn])['LoadBalancers'][0]['IpAddressType']
+    except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
+        module.fail_json_aws(e, msg="Failed to describe load balancer ip address type")
+
+
+def list_load_balancers(connection, module):
+    load_balancer_arns = module.params.get("load_balancer_arns")
+    names = module.params.get("names")
+
+    try:
+        if not load_balancer_arns and not names:
+            load_balancers = get_paginator(connection)
+        if load_balancer_arns:
+            load_balancers = get_paginator(connection, LoadBalancerArns=load_balancer_arns)
+        if names:
+            load_balancers = get_paginator(connection, Names=names)
+    except is_boto3_error_code('LoadBalancerNotFound'):
+        module.exit_json(load_balancers=[])
+    except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:  # pylint: disable=duplicate-except
+        module.fail_json_aws(e, msg="Failed to list load balancers")
+
+    for load_balancer in load_balancers['LoadBalancers']:
+        # Get the attributes for each alb
+        load_balancer.update(get_load_balancer_attributes(connection, module, load_balancer['LoadBalancerArn']))
+
+        # Get the listeners for each alb
+        load_balancer['listeners'] = get_alb_listeners(connection, module, load_balancer['LoadBalancerArn'])
+
+        # For each listener, get listener rules
+        for listener in load_balancer['listeners']:
+            listener['rules'] = get_listener_rules(connection, module, listener['ListenerArn'])
+
+        # Get ALB ip address type
+        load_balancer['IpAddressType'] = get_load_balancer_ipaddresstype(connection, module, load_balancer['LoadBalancerArn'])
+
+    # Turn the boto3 result in to ansible_friendly_snaked_names
+    snaked_load_balancers = [camel_dict_to_snake_dict(load_balancer) for load_balancer in load_balancers['LoadBalancers']]
+
+    # Get tags for each load balancer
+    for snaked_load_balancer in snaked_load_balancers:
+        snaked_load_balancer['tags'] = get_load_balancer_tags(connection, module, snaked_load_balancer['load_balancer_arn'])
+
+    module.exit_json(load_balancers=snaked_load_balancers)
+
+
+def main():
+
+    argument_spec = dict(
+        load_balancer_arns=dict(type='list', elements='str'),
+        names=dict(type='list', elements='str')
+    )
+
+    module = AnsibleAWSModule(
+        argument_spec=argument_spec,
+        mutually_exclusive=[['load_balancer_arns', 'names']],
+        supports_check_mode=True,
+    )
+
+    try:
+        connection = module.client('elbv2', retry_decorator=AWSRetry.jittered_backoff(retries=10))
+    except (botocore.exceptions.ClientError, botocore.exceptions.BotoCoreError) as e:
+        module.fail_json_aws(e, msg='Failed to connect to AWS')
+
+    list_load_balancers(connection, module)
+
+
+if __name__ == '__main__':
+    main()


Test patch:
diff --git a/tests/integration/targets/elb_application_lb/aliases b/tests/integration/targets/elb_application_lb/aliases
new file mode 100644
--- /dev/null
+++ b/tests/integration/targets/elb_application_lb/aliases
@@ -0,0 +1,3 @@
+cloud/aws
+slow
+elb_application_lb_info
\ No newline at end of file
diff --git a/tests/integration/targets/elb_application_lb/defaults/main.yml b/tests/integration/targets/elb_application_lb/defaults/main.yml
new file mode 100644
--- /dev/null
+++ b/tests/integration/targets/elb_application_lb/defaults/main.yml
@@ -0,0 +1,10 @@
+# defaults file for elb_application_lb
+
+resource_short: "{{ '%0.8x'%((16**8) | random(seed=resource_prefix)) }}"
+alb_name: alb-test-{{ resource_short }}
+tg_name: alb-test-{{ resource_short }}
+vpc_cidr: 10.{{ 256 | random(seed=resource_prefix) }}.0.0/16
+private_subnet_cidr_1: 10.{{ 256 | random(seed=resource_prefix) }}.1.0/24
+private_subnet_cidr_2: 10.{{ 256 | random(seed=resource_prefix) }}.2.0/24
+public_subnet_cidr_1: 10.{{ 256 | random(seed=resource_prefix) }}.3.0/24
+public_subnet_cidr_2: 10.{{ 256 | random(seed=resource_prefix) }}.4.0/24
diff --git a/tests/integration/targets/elb_application_lb/tasks/main.yml b/tests/integration/targets/elb_application_lb/tasks/main.yml
new file mode 100644
--- /dev/null
+++ b/tests/integration/targets/elb_application_lb/tasks/main.yml
@@ -0,0 +1,1354 @@
+- name: elb_application_lb integration tests
+  module_defaults:
+    group/aws:
+      aws_access_key: '{{ aws_access_key }}'
+      aws_secret_key: '{{ aws_secret_key }}'
+      security_token: '{{ security_token | default(omit) }}'
+      region: '{{ aws_region }}'
+  block:
+  - name: Create a test VPC
+    ec2_vpc_net:
+      cidr_block: '{{ vpc_cidr }}'
+      name: '{{ resource_prefix }}_vpc'
+      state: present
+      ipv6_cidr: true
+      tags:
+        Name: elb_application_lb testing
+        ResourcePrefix: '{{ resource_prefix }}'
+    register: vpc
+  - name: 'Set fact: VPC ID'
+    set_fact:
+      vpc_id: '{{ vpc.vpc.id }}'
+  - name: Get VPC's default security group
+    ec2_group_info:
+      filters:
+        vpc-id: '{{ vpc_id }}'
+    register: default_sg
+  - name: Create an internet gateway
+    ec2_vpc_igw:
+      vpc_id: '{{ vpc_id }}'
+      state: present
+      tags:
+        Name: '{{ resource_prefix }}'
+    register: igw
+  - name: Create private subnets
+    ec2_vpc_subnet:
+      cidr: '{{ item.cidr }}'
+      az: '{{ aws_region }}{{ item.az }}'
+      vpc_id: '{{ vpc_id }}'
+      state: present
+      tags:
+        Public: 'False'
+        Name: private-{{ item.az }}
+    with_items:
+    - cidr: '{{ private_subnet_cidr_1 }}'
+      az: a
+    - cidr: '{{ private_subnet_cidr_2 }}'
+      az: b
+    register: private_subnets
+  - name: Create public subnets with ipv6
+    ec2_vpc_subnet:
+      cidr: '{{ item.cidr }}'
+      az: '{{ aws_region }}{{ item.az }}'
+      vpc_id: '{{ vpc_id }}'
+      state: present
+      ipv6_cidr: '{{ item.vpc_ipv6_cidr }}'
+      tags:
+        Public: 'True'
+        Name: public-{{ item.az }}
+    with_items:
+    - cidr: '{{ public_subnet_cidr_1 }}'
+      az: a
+      vpc_ipv6_cidr: "{{ vpc.vpc.ipv6_cidr_block_association_set[0].ipv6_cidr_block\
+        \ | replace('0::/56','0::/64') }}"
+    - cidr: '{{ public_subnet_cidr_2 }}'
+      az: b
+      vpc_ipv6_cidr: "{{ vpc.vpc.ipv6_cidr_block_association_set[0].ipv6_cidr_block\
+        \ | replace('0::/56','1::/64') }}"
+    register: public_subnets
+  - name: Create list of subnet ids
+    set_fact:
+      public_subnets: "{{ public_subnets.results | map(attribute='subnet') | map(attribute='id')\
+        \ }}"
+      private_subnets: "{{ private_subnets.results | map(attribute='subnet') | map(attribute='id')\
+        \ }}"
+  - name: Create a route table
+    ec2_vpc_route_table:
+      vpc_id: '{{ vpc_id }}'
+      tags:
+        Name: igw-route
+        Created: '{{ resource_prefix }}'
+      subnets: '{{ public_subnets + private_subnets }}'
+      routes:
+      - dest: 0.0.0.0/0
+        gateway_id: '{{ igw.gateway_id }}'
+    register: route_table
+  - name: Create a security group for Ansible ALB integration tests
+    ec2_group:
+      name: '{{ resource_prefix }}'
+      description: security group for Ansible ALB integration tests
+      state: present
+      vpc_id: '{{ vpc_id }}'
+      rules:
+      - proto: tcp
+        from_port: 1
+        to_port: 65535
+        cidr_ip: 0.0.0.0/0
+    register: sec_group
+  - name: Create another security group for Ansible ALB integration tests
+    ec2_group:
+      name: '{{ resource_prefix }}-2'
+      description: security group for Ansible ALB integration tests
+      state: present
+      vpc_id: '{{ vpc_id }}'
+      rules:
+      - proto: tcp
+        from_port: 1
+        to_port: 65535
+        cidr_ip: 0.0.0.0/0
+    register: sec_group2
+  - name: Create a target group for testing
+    elb_target_group:
+      name: '{{ tg_name }}'
+      protocol: http
+      port: 80
+      vpc_id: '{{ vpc_id }}'
+      state: present
+    register: tg
+  - name: Create an ALB (invalid - SslPolicy is required when Protocol == HTTPS)
+    elb_application_lb:
+      name: '{{ alb_name }}'
+      subnets: '{{ public_subnets }}'
+      security_groups: '{{ sec_group.group_id }}'
+      state: present
+      listeners:
+      - Protocol: HTTPS
+        Port: 80
+        DefaultActions:
+        - Type: forward
+          TargetGroupName: '{{ tg_name }}'
+    ignore_errors: yes
+    register: alb
+  - assert:
+      that:
+      - alb is failed
+      - alb.msg is match("'SslPolicy' is a required listener dict key when Protocol
+        = HTTPS")
+
+  - name: Create an ALB (invalid - didn't provide required listener options)
+    elb_application_lb:
+      name: '{{ alb_name }}'
+      subnets: '{{ public_subnets }}'
+      security_groups: '{{ sec_group.group_id }}'
+      state: present
+      listeners:
+      - Port: 80
+    ignore_errors: yes
+    register: alb
+  - assert:
+      that:
+      - alb is failed
+      - alb.msg is match("missing required arguments:\ DefaultActions, Protocol found
+        in listeners")
+
+  - name: Create an ALB (invalid - invalid listener option type)
+    elb_application_lb:
+      name: '{{ alb_name }}'
+      subnets: '{{ public_subnets }}'
+      security_groups: '{{ sec_group.group_id }}'
+      state: present
+      listeners:
+      - Protocol: HTTP
+        Port: bad type
+        DefaultActions:
+        - Type: forward
+          TargetGroupName: '{{ tg_name }}'
+    ignore_errors: yes
+    register: alb
+  - assert:
+      that:
+      - alb is failed
+      - "'unable to convert to int' in alb.msg"
+
+  - name: Create an ALB (invalid - invalid ip address type)
+    elb_application_lb:
+      name: '{{ alb_name }}'
+      subnets: '{{ public_subnets }}'
+      security_groups: '{{ sec_group.group_id }}'
+      state: present
+      listeners:
+      - Protocol: HTTP
+        Port: 80
+        DefaultActions:
+        - Type: forward
+          TargetGroupName: '{{ tg_name }}'
+      ip_address_type: ip_addr_v4_v6
+    ignore_errors: yes
+    register: alb
+  - assert:
+      that:
+      - alb is failed
+
+    # ------------------------------------------------------------------------------------------
+
+  - name: Create an ALB with defaults - check_mode
+    elb_application_lb:
+      name: '{{ alb_name }}'
+      subnets: '{{ public_subnets }}'
+      security_groups: []
+      state: present
+      listeners:
+      - Protocol: HTTP
+        Port: 80
+        DefaultActions:
+        - Type: forward
+          TargetGroupName: '{{ tg_name }}'
+    register: alb
+    check_mode: yes
+  - assert:
+      that:
+      - alb is changed
+      - alb.msg is match('Would have created ALB if not in check mode.')
+
+  - name: Create an ALB with defaults
+    elb_application_lb:
+      name: '{{ alb_name }}'
+      subnets: '{{ public_subnets }}'
+      security_groups: []
+      state: present
+      listeners:
+      - Protocol: HTTP
+        Port: 80
+        DefaultActions:
+        - Type: forward
+          TargetGroupName: '{{ tg_name }}'
+    register: alb
+  - assert:
+      that:
+      - alb is changed
+      - alb.listeners[0].rules | length == 1
+      - alb.security_groups | length == 1
+      - alb.security_groups[0] == default_sg.security_groups[0].group_id
+
+  - name: Create an ALB with defaults (idempotence) - check_mode
+    elb_application_lb:
+      name: '{{ alb_name }}'
+      subnets: '{{ public_subnets }}'
+      security_groups: []
+      state: present
+      listeners:
+      - Protocol: HTTP
+        Port: 80
+        DefaultActions:
+        - Type: forward
+          TargetGroupName: '{{ tg_name }}'
+    register: alb
+    check_mode: yes
+  - assert:
+      that:
+      - alb is not changed
+      - alb.msg is match('IN CHECK MODE - no changes to make to ALB specified.')
+
+  - name: Create an ALB with defaults (idempotence)
+    elb_application_lb:
+      name: '{{ alb_name }}'
+      subnets: '{{ public_subnets }}'
+      security_groups: []
+      state: present
+      listeners:
+      - Protocol: HTTP
+        Port: 80
+        DefaultActions:
+        - Type: forward
+          TargetGroupName: '{{ tg_name }}'
+    register: alb
+  - assert:
+      that:
+      - alb is not changed
+      - alb.listeners[0].rules | length == 1
+      - alb.security_groups[0] == default_sg.security_groups[0].group_id
+
+    # ------------------------------------------------------------------------------------------
+
+  - name: Update an ALB with ip address type - check_mode
+    elb_application_lb:
+      name: '{{ alb_name }}'
+      subnets: '{{ public_subnets }}'
+      security_groups: '{{ sec_group.group_id }}'
+      state: present
+      listeners:
+      - Protocol: HTTP
+        Port: 80
+        DefaultActions:
+        - Type: forward
+          TargetGroupName: '{{ tg_name }}'
+      ip_address_type: dualstack
+    register: alb
+    check_mode: yes
+  - assert:
+      that:
+      - alb is changed
+      - alb.msg is match('Would have updated ALB if not in check mode.')
+
+  - name: Update an ALB with ip address type
+    elb_application_lb:
+      name: '{{ alb_name }}'
+      subnets: '{{ public_subnets }}'
+      security_groups: '{{ sec_group.group_id }}'
+      state: present
+      listeners:
+      - Protocol: HTTP
+        Port: 80
+        DefaultActions:
+        - Type: forward
+          TargetGroupName: '{{ tg_name }}'
+      ip_address_type: dualstack
+    register: alb
+  - assert:
+      that:
+      - alb is changed
+      - alb.ip_address_type == 'dualstack'
+      - alb.listeners[0].rules | length == 1
+      - alb.routing_http2_enabled | bool
+      - alb.routing_http_desync_mitigation_mode == 'defensive'
+      - not alb.routing_http_drop_invalid_header_fields_enabled | bool
+      - not alb.routing_http_x_amzn_tls_version_and_cipher_suite_enabled | bool
+      - not alb.routing_http_xff_client_port_enabled | bool
+      - not alb.waf_fail_open_enabled | bool
+
+  - name: Create an ALB with ip address type (idempotence) - check_mode
+    elb_application_lb:
+      name: '{{ alb_name }}'
+      subnets: '{{ public_subnets }}'
+      security_groups: '{{ sec_group.group_id }}'
+      state: present
+      listeners:
+      - Protocol: HTTP
+        Port: 80
+        DefaultActions:
+        - Type: forward
+          TargetGroupName: '{{ tg_name }}'
+      ip_address_type: dualstack
+    register: alb
+    check_mode: yes
+  - assert:
+      that:
+      - alb is not changed
+      - alb.msg is match('IN CHECK MODE - no changes to make to ALB specified.')
+
+  - name: Create an ALB with ip address type (idempotence)
+    elb_application_lb:
+      name: '{{ alb_name }}'
+      subnets: '{{ public_subnets }}'
+      security_groups: '{{ sec_group.group_id }}'
+      state: present
+      listeners:
+      - Protocol: HTTP
+        Port: 80
+        DefaultActions:
+        - Type: forward
+          TargetGroupName: '{{ tg_name }}'
+      ip_address_type: dualstack
+    register: alb
+  - assert:
+      that:
+      - alb is not changed
+      - alb.ip_address_type == 'dualstack'
+      - alb.routing_http2_enabled | bool
+      - alb.routing_http_desync_mitigation_mode == 'defensive'
+      - not alb.routing_http_drop_invalid_header_fields_enabled | bool
+      - not alb.routing_http_x_amzn_tls_version_and_cipher_suite_enabled | bool
+      - not alb.routing_http_xff_client_port_enabled | bool
+      - not alb.waf_fail_open_enabled | bool
+
+    # ------------------------------------------------------------------------------------------
+
+  - name: Update an ALB with different attributes - check_mode
+    elb_application_lb:
+      name: '{{ alb_name }}'
+      subnets: '{{ public_subnets }}'
+      security_groups: '{{ sec_group.group_id }}'
+      state: present
+      listeners:
+      - Protocol: HTTP
+        Port: 80
+        DefaultActions:
+        - Type: forward
+          TargetGroupName: '{{ tg_name }}'
+      ip_address_type: dualstack
+      http2: no
+      http_desync_mitigation_mode: monitor
+      http_drop_invalid_header_fields: yes
+      http_x_amzn_tls_version_and_cipher_suite: yes
+      http_xff_client_port: yes
+      waf_fail_open: yes
+    register: alb
+    check_mode: yes
+  - assert:
+      that:
+      - alb is changed
+      - alb.msg is match('Would have updated ALB if not in check mode.')
+
+  - name: Update an ALB with different attributes
+    elb_application_lb:
+      name: '{{ alb_name }}'
+      subnets: '{{ public_subnets }}'
+      security_groups: '{{ sec_group.group_id }}'
+      state: present
+      listeners:
+      - Protocol: HTTP
+        Port: 80
+        DefaultActions:
+        - Type: forward
+          TargetGroupName: '{{ tg_name }}'
+      ip_address_type: dualstack
+      http2: no
+      http_desync_mitigation_mode: monitor
+      http_drop_invalid_header_fields: yes
+      http_x_amzn_tls_version_and_cipher_suite: yes
+      http_xff_client_port: yes
+      waf_fail_open: yes
+    register: alb
+  - assert:
+      that:
+      - alb is changed
+      - alb.ip_address_type == 'dualstack'
+      - not alb.routing_http2_enabled | bool
+      - alb.routing_http_desync_mitigation_mode == 'monitor'
+      - alb.routing_http_drop_invalid_header_fields_enabled | bool
+      - alb.routing_http_x_amzn_tls_version_and_cipher_suite_enabled | bool
+      - alb.routing_http_xff_client_port_enabled | bool
+      - alb.waf_fail_open_enabled | bool
+
+  - name: Update an ALB with different attributes (idempotence) - check_mode
+    elb_application_lb:
+      name: '{{ alb_name }}'
+      subnets: '{{ public_subnets }}'
+      security_groups: '{{ sec_group.group_id }}'
+      state: present
+      listeners:
+      - Protocol: HTTP
+        Port: 80
+        DefaultActions:
+        - Type: forward
+          TargetGroupName: '{{ tg_name }}'
+      ip_address_type: dualstack
+      http2: no
+      http_desync_mitigation_mode: monitor
+      http_drop_invalid_header_fields: yes
+      http_x_amzn_tls_version_and_cipher_suite: yes
+      http_xff_client_port: yes
+      waf_fail_open: yes
+    register: alb
+    check_mode: yes
+  - assert:
+      that:
+      - alb is not changed
+      - alb.msg is match('IN CHECK MODE - no changes to make to ALB specified.')
+
+  - name: Update an ALB with different attributes (idempotence)
+    elb_application_lb:
+      name: '{{ alb_name }}'
+      subnets: '{{ public_subnets }}'
+      security_groups: '{{ sec_group.group_id }}'
+      state: present
+      listeners:
+      - Protocol: HTTP
+        Port: 80
+        DefaultActions:
+        - Type: forward
+          TargetGroupName: '{{ tg_name }}'
+      ip_address_type: dualstack
+      http2: no
+      http_desync_mitigation_mode: monitor
+      http_drop_invalid_header_fields: yes
+      http_x_amzn_tls_version_and_cipher_suite: yes
+      http_xff_client_port: yes
+      waf_fail_open: yes
+    register: alb
+  - assert:
+      that:
+      - alb is not changed
+      - alb.ip_address_type == 'dualstack'
+      - not alb.routing_http2_enabled | bool
+      - alb.routing_http_desync_mitigation_mode == 'monitor'
+      - alb.routing_http_drop_invalid_header_fields_enabled | bool
+      - alb.routing_http_x_amzn_tls_version_and_cipher_suite_enabled | bool
+      - alb.routing_http_xff_client_port_enabled | bool
+      - alb.waf_fail_open_enabled | bool
+
+    # ------------------------------------------------------------------------------------------
+
+  - name: Update an ALB with different ip address type - check_mode
+    elb_application_lb:
+      name: '{{ alb_name }}'
+      subnets: '{{ public_subnets }}'
+      security_groups: '{{ sec_group.group_id }}'
+      state: present
+      listeners:
+      - Protocol: HTTP
+        Port: 80
+        DefaultActions:
+        - Type: forward
+          TargetGroupName: '{{ tg_name }}'
+      ip_address_type: ipv4
+      http2: no
+      http_desync_mitigation_mode: monitor
+      http_drop_invalid_header_fields: yes
+      http_x_amzn_tls_version_and_cipher_suite: yes
+      http_xff_client_port: yes
+      waf_fail_open: yes
+    register: alb
+    check_mode: yes
+  - assert:
+      that:
+      - alb is changed
+      - alb.msg is match('Would have updated ALB if not in check mode.')
+
+  - name: Update an ALB with different ip address type
+    elb_application_lb:
+      name: '{{ alb_name }}'
+      subnets: '{{ public_subnets }}'
+      security_groups: '{{ sec_group.group_id }}'
+      state: present
+      listeners:
+      - Protocol: HTTP
+        Port: 80
+        DefaultActions:
+        - Type: forward
+          TargetGroupName: '{{ tg_name }}'
+      ip_address_type: ipv4
+      http2: no
+      http_desync_mitigation_mode: monitor
+      http_drop_invalid_header_fields: yes
+      http_x_amzn_tls_version_and_cipher_suite: yes
+      http_xff_client_port: yes
+      waf_fail_open: yes
+    register: alb
+  - assert:
+      that:
+      - alb is changed
+      - alb.ip_address_type == 'ipv4'
+      - not alb.routing_http2_enabled | bool
+      - alb.routing_http_desync_mitigation_mode == 'monitor'
+      - alb.routing_http_drop_invalid_header_fields_enabled | bool
+      - alb.routing_http_x_amzn_tls_version_and_cipher_suite_enabled | bool
+      - alb.routing_http_xff_client_port_enabled | bool
+      - alb.waf_fail_open_enabled | bool
+
+  - name: Update an ALB with different ip address type (idempotence) - check_mode
+    elb_application_lb:
+      name: '{{ alb_name }}'
+      subnets: '{{ public_subnets }}'
+      security_groups: '{{ sec_group.group_id }}'
+      state: present
+      listeners:
+      - Protocol: HTTP
+        Port: 80
+        DefaultActions:
+        - Type: forward
+          TargetGroupName: '{{ tg_name }}'
+      ip_address_type: ipv4
+      http2: no
+      http_desync_mitigation_mode: monitor
+      http_drop_invalid_header_fields: yes
+      http_x_amzn_tls_version_and_cipher_suite: yes
+      http_xff_client_port: yes
+      waf_fail_open: yes
+    register: alb
+    check_mode: yes
+  - assert:
+      that:
+      - alb is not changed
+      - alb.msg is match('IN CHECK MODE - no changes to make to ALB specified.')
+
+  - name: Update an ALB with different ip address type (idempotence)
+    elb_application_lb:
+      name: '{{ alb_name }}'
+      subnets: '{{ public_subnets }}'
+      security_groups: '{{ sec_group.group_id }}'
+      state: present
+      listeners:
+      - Protocol: HTTP
+        Port: 80
+        DefaultActions:
+        - Type: forward
+          TargetGroupName: '{{ tg_name }}'
+      ip_address_type: ipv4
+      http2: no
+      http_desync_mitigation_mode: monitor
+      http_drop_invalid_header_fields: yes
+      http_x_amzn_tls_version_and_cipher_suite: yes
+      http_xff_client_port: yes
+      waf_fail_open: yes
+    register: alb
+  - assert:
+      that:
+      - alb is not changed
+      - alb.ip_address_type == 'ipv4'
+      - not alb.routing_http2_enabled | bool
+      - alb.routing_http_desync_mitigation_mode == 'monitor'
+      - alb.routing_http_drop_invalid_header_fields_enabled | bool
+      - alb.routing_http_x_amzn_tls_version_and_cipher_suite_enabled | bool
+      - alb.routing_http_xff_client_port_enabled | bool
+      - alb.waf_fail_open_enabled | bool
+
+    # ------------------------------------------------------------------------------------------
+
+  - name: Update an ALB with different listener by adding rule - check_mode
+    elb_application_lb:
+      name: '{{ alb_name }}'
+      subnets: '{{ public_subnets }}'
+      security_groups: '{{ sec_group.group_id }}'
+      state: present
+      listeners:
+      - Protocol: HTTP
+        Port: 80
+        DefaultActions:
+        - Type: forward
+          TargetGroupName: '{{ tg_name }}'
+        Rules:
+        - Conditions:
+          - Field: path-pattern
+            Values:
+            - /test
+          Priority: '1'
+          Actions:
+          - TargetGroupName: '{{ tg_name }}'
+            Type: forward
+    register: alb
+    check_mode: yes
+  - assert:
+      that:
+      - alb is changed
+      - alb.msg is match('Would have updated ALB if not in check mode.')
+
+  - name: Update an ALB with different listener by adding rule
+    elb_application_lb:
+      name: '{{ alb_name }}'
+      subnets: '{{ public_subnets }}'
+      security_groups: '{{ sec_group.group_id }}'
+      state: present
+      listeners:
+      - Protocol: HTTP
+        Port: 80
+        DefaultActions:
+        - Type: forward
+          TargetGroupName: '{{ tg_name }}'
+        Rules:
+        - Conditions:
+          - Field: path-pattern
+            Values:
+            - /test
+          Priority: '1'
+          Actions:
+          - TargetGroupName: '{{ tg_name }}'
+            Type: forward
+    register: alb
+  - assert:
+      that:
+      - alb is changed
+      - alb.listeners[0].rules | length == 2
+      - "'1' in {{ alb.listeners[0].rules | map(attribute='priority') }}"
+
+  - name: Update an ALB with different listener by adding rule (idempotence) - check_mode
+    elb_application_lb:
+      name: '{{ alb_name }}'
+      subnets: '{{ public_subnets }}'
+      security_groups: '{{ sec_group.group_id }}'
+      state: present
+      listeners:
+      - Protocol: HTTP
+        Port: 80
+        DefaultActions:
+        - Type: forward
+          TargetGroupName: '{{ tg_name }}'
+        Rules:
+        - Conditions:
+          - Field: path-pattern
+            Values:
+            - /test
+          Priority: '1'
+          Actions:
+          - TargetGroupName: '{{ tg_name }}'
+            Type: forward
+    register: alb
+    check_mode: yes
+  - assert:
+      that:
+      - alb is not changed
+      - alb.msg is match('IN CHECK MODE - no changes to make to ALB specified.')
+
+  - name: Update an ALB with different listener by adding rule (idempotence)
+    elb_application_lb:
+      name: '{{ alb_name }}'
+      subnets: '{{ public_subnets }}'
+      security_groups: '{{ sec_group.group_id }}'
+      state: present
+      listeners:
+      - Protocol: HTTP
+        Port: 80
+        DefaultActions:
+        - Type: forward
+          TargetGroupName: '{{ tg_name }}'
+        Rules:
+        - Conditions:
+          - Field: path-pattern
+            Values:
+            - /test
+          Priority: '1'
+          Actions:
+          - TargetGroupName: '{{ tg_name }}'
+            Type: forward
+    register: alb
+  - assert:
+      that:
+      - alb is not changed
+      - alb.listeners[0].rules | length == 2
+      - "'1' in {{ alb.listeners[0].rules | map(attribute='priority') }}"
+
+    # ------------------------------------------------------------------------------------------
+
+  - name: Update an ALB with different listener by modifying rule - check_mode
+    elb_application_lb:
+      name: '{{ alb_name }}'
+      subnets: '{{ public_subnets }}'
+      security_groups: '{{ sec_group.group_id }}'
+      state: present
+      listeners:
+      - Protocol: HTTP
+        Port: 80
+        DefaultActions:
+        - Type: forward
+          TargetGroupName: '{{ tg_name }}'
+        Rules:
+        - Conditions:
+          - Field: path-pattern
+            Values:
+            - /test
+          Priority: '2'
+          Actions:
+          - TargetGroupName: '{{ tg_name }}'
+            Type: forward
+    register: alb
+    check_mode: yes
+  - assert:
+      that:
+      - alb is changed
+      - alb.msg is match('Would have updated ALB if not in check mode.')
+
+  - name: Update an ALB with different listener by modifying rule
+    elb_application_lb:
+      name: '{{ alb_name }}'
+      subnets: '{{ public_subnets }}'
+      security_groups: '{{ sec_group.group_id }}'
+      state: present
+      listeners:
+      - Protocol: HTTP
+        Port: 80
+        DefaultActions:
+        - Type: forward
+          TargetGroupName: '{{ tg_name }}'
+        Rules:
+        - Conditions:
+          - Field: path-pattern
+            Values:
+            - /test
+          Priority: '2'
+          Actions:
+          - TargetGroupName: '{{ tg_name }}'
+            Type: forward
+    register: alb
+  - assert:
+      that:
+      - alb is changed
+      - alb.listeners[0].rules | length == 2
+      - "'2' in {{ alb.listeners[0].rules | map(attribute='priority') }}"
+
+  - name: Update an ALB with different listener by modifying rule (idempotence) -
+      check_mode
+    elb_application_lb:
+      name: '{{ alb_name }}'
+      subnets: '{{ public_subnets }}'
+      security_groups: '{{ sec_group.group_id }}'
+      state: present
+      listeners:
+      - Protocol: HTTP
+        Port: 80
+        DefaultActions:
+        - Type: forward
+          TargetGroupName: '{{ tg_name }}'
+        Rules:
+        - Conditions:
+          - Field: path-pattern
+            Values:
+            - /test
+          Priority: '2'
+          Actions:
+          - TargetGroupName: '{{ tg_name }}'
+            Type: forward
+    register: alb
+    check_mode: yes
+  - assert:
+      that:
+      - alb is not changed
+      - alb.msg is match('IN CHECK MODE - no changes to make to ALB specified.')
+
+  - name: Update an ALB with different listener by modifying rule (idempotence)
+    elb_application_lb:
+      name: '{{ alb_name }}'
+      subnets: '{{ public_subnets }}'
+      security_groups: '{{ sec_group.group_id }}'
+      state: present
+      listeners:
+      - Protocol: HTTP
+        Port: 80
+        DefaultActions:
+        - Type: forward
+          TargetGroupName: '{{ tg_name }}'
+        Rules:
+        - Conditions:
+          - Field: path-pattern
+            Values:
+            - /test
+          Priority: '2'
+          Actions:
+          - TargetGroupName: '{{ tg_name }}'
+            Type: forward
+    register: alb
+  - assert:
+      that:
+      - alb is not changed
+      - alb.listeners[0].rules | length == 2
+      - "'2' in {{ alb.listeners[0].rules | map(attribute='priority') }}"
+
+    # ------------------------------------------------------------------------------------------
+
+  - name: Update an ALB with different listener by deleting rule - check_mode
+    elb_application_lb:
+      name: '{{ alb_name }}'
+      subnets: '{{ public_subnets }}'
+      security_groups: '{{ sec_group.group_id }}'
+      state: present
+      listeners:
+      - Protocol: HTTP
+        Port: 80
+        DefaultActions:
+        - Type: forward
+          TargetGroupName: '{{ tg_name }}'
+        Rules: []
+    register: alb
+    check_mode: yes
+  - assert:
+      that:
+      - alb is changed
+      - alb.msg is match('Would have updated ALB if not in check mode.')
+
+  - name: Update an ALB with different listener by deleting rule
+    elb_application_lb:
+      name: '{{ alb_name }}'
+      subnets: '{{ public_subnets }}'
+      security_groups: '{{ sec_group.group_id }}'
+      state: present
+      listeners:
+      - Protocol: HTTP
+        Port: 80
+        DefaultActions:
+        - Type: forward
+          TargetGroupName: '{{ tg_name }}'
+        Rules: []
+    register: alb
+  - assert:
+      that:
+      - alb is changed
+      - alb.listeners[0].rules | length == 1
+      - "'2' not in {{ alb.listeners[0].rules | map(attribute='priority') }}"
+
+  - name: Update an ALB with different listener by deleting rule (idempotence) - check_mode
+    elb_application_lb:
+      name: '{{ alb_name }}'
+      subnets: '{{ public_subnets }}'
+      security_groups: '{{ sec_group.group_id }}'
+      state: present
+      listeners:
+      - Protocol: HTTP
+        Port: 80
+        DefaultActions:
+        - Type: forward
+          TargetGroupName: '{{ tg_name }}'
+        Rules: []
+    register: alb
+    check_mode: yes
+  - assert:
+      that:
+      - alb is not changed
+      - alb.msg is match('IN CHECK MODE - no changes to make to ALB specified.')
+
+  - name: Update an ALB with different listener by deleting rule (idempotence)
+    elb_application_lb:
+      name: '{{ alb_name }}'
+      subnets: '{{ public_subnets }}'
+      security_groups: '{{ sec_group.group_id }}'
+      state: present
+      listeners:
+      - Protocol: HTTP
+        Port: 80
+        DefaultActions:
+        - Type: forward
+          TargetGroupName: '{{ tg_name }}'
+        Rules: []
+    register: alb
+  - assert:
+      that:
+      - alb is not changed
+      - alb.listeners[0].rules | length == 1
+      - "'2' not in {{ alb.listeners[0].rules | map(attribute='priority') }}"
+
+    # ------------------------------------------------------------------------------------------
+
+  - name: Update an ALB by deleting listener - check_mode
+    elb_application_lb:
+      name: '{{ alb_name }}'
+      subnets: '{{ public_subnets }}'
+      security_groups: '{{ sec_group.group_id }}'
+      state: present
+      listeners: []
+    register: alb
+    check_mode: yes
+  - assert:
+      that:
+      - alb is changed
+      - alb.msg is match('Would have updated ALB if not in check mode.')
+
+  - name: Update an ALB by deleting listener
+    elb_application_lb:
+      name: '{{ alb_name }}'
+      subnets: '{{ public_subnets }}'
+      security_groups: '{{ sec_group.group_id }}'
+      state: present
+      listeners: []
+    register: alb
+  - assert:
+      that:
+      - alb is changed
+      - not alb.listeners
+
+  - name: Update an ALB by deleting listener (idempotence) - check_mode
+    elb_application_lb:
+      name: '{{ alb_name }}'
+      subnets: '{{ public_subnets }}'
+      security_groups: '{{ sec_group.group_id }}'
+      state: present
+      listeners: []
+    register: alb
+    check_mode: yes
+  - assert:
+      that:
+      - alb is not changed
+      - alb.msg is match('IN CHECK MODE - no changes to make to ALB specified.')
+
+  - name: Update an ALB by deleting listener (idempotence)
+    elb_application_lb:
+      name: '{{ alb_name }}'
+      subnets: '{{ public_subnets }}'
+      security_groups: '{{ sec_group.group_id }}'
+      state: present
+      listeners: []
+    register: alb
+  - assert:
+      that:
+      - alb is not changed
+      - not alb.listeners
+
+    # ------------------------------------------------------------------------------------------
+
+  - name: Update an ALB by adding tags - check_mode
+    elb_application_lb:
+      name: '{{ alb_name }}'
+      subnets: '{{ public_subnets }}'
+      security_groups: '{{ sec_group.group_id }}'
+      state: present
+      tags:
+        created_by: ALB test {{ resource_prefix }}
+    register: alb
+    check_mode: yes
+  - assert:
+      that:
+      - alb is changed
+      - alb.msg is match('Would have updated ALB if not in check mode.')
+
+  - name: Update an ALB by adding tags
+    elb_application_lb:
+      name: '{{ alb_name }}'
+      subnets: '{{ public_subnets }}'
+      security_groups: '{{ sec_group.group_id }}'
+      state: present
+      tags:
+        created_by: ALB test {{ resource_prefix }}
+    register: alb
+  - assert:
+      that:
+      - alb is changed
+      - 'alb.tags == {"created_by": "ALB test {{ resource_prefix }}"}'
+
+  - name: Update an ALB by adding tags (idempotence) - check_mode
+    elb_application_lb:
+      name: '{{ alb_name }}'
+      subnets: '{{ public_subnets }}'
+      security_groups: '{{ sec_group.group_id }}'
+      state: present
+      tags:
+        created_by: ALB test {{ resource_prefix }}
+    register: alb
+    check_mode: yes
+  - assert:
+      that:
+      - alb is not changed
+      - alb.msg is match('IN CHECK MODE - no changes to make to ALB specified.')
+
+  - name: Update an ALB by adding tags (idempotence)
+    elb_application_lb:
+      name: '{{ alb_name }}'
+      subnets: '{{ public_subnets }}'
+      security_groups: '{{ sec_group.group_id }}'
+      state: present
+      tags:
+        created_by: ALB test {{ resource_prefix }}
+    register: alb
+  - assert:
+      that:
+      - alb is not changed
+      - 'alb.tags == {"created_by": "ALB test {{ resource_prefix }}"}'
+
+    # ------------------------------------------------------------------------------------------
+
+  - name: Update an ALB by modifying tags - check_mode
+    elb_application_lb:
+      name: '{{ alb_name }}'
+      subnets: '{{ public_subnets }}'
+      security_groups: '{{ sec_group.group_id }}'
+      state: present
+      tags:
+        created_by: ALB test {{ resource_prefix }}-2
+    register: alb
+    check_mode: yes
+  - assert:
+      that:
+      - alb is changed
+      - alb.msg is match('Would have updated ALB if not in check mode.')
+
+  - name: Update an ALB by modifying tags
+    elb_application_lb:
+      name: '{{ alb_name }}'
+      subnets: '{{ public_subnets }}'
+      security_groups: '{{ sec_group.group_id }}'
+      state: present
+      tags:
+        created_by: ALB test {{ resource_prefix }}-2
+    register: alb
+  - assert:
+      that:
+      - alb is changed
+      - 'alb.tags == {"created_by": "ALB test {{ resource_prefix }}-2"}'
+
+  - name: Update an ALB by modifying tags (idempotence) - check_mode
+    elb_application_lb:
+      name: '{{ alb_name }}'
+      subnets: '{{ public_subnets }}'
+      security_groups: '{{ sec_group.group_id }}'
+      state: present
+      tags:
+        created_by: ALB test {{ resource_prefix }}-2
+    register: alb
+    check_mode: yes
+  - assert:
+      that:
+      - alb is not changed
+      - alb.msg is match('IN CHECK MODE - no changes to make to ALB specified.')
+
+  - name: Update an ALB by modifying tags (idempotence)
+    elb_application_lb:
+      name: '{{ alb_name }}'
+      subnets: '{{ public_subnets }}'
+      security_groups: '{{ sec_group.group_id }}'
+      state: present
+      tags:
+        created_by: ALB test {{ resource_prefix }}-2
+    register: alb
+  - assert:
+      that:
+      - alb is not changed
+      - 'alb.tags == {"created_by": "ALB test {{ resource_prefix }}-2"}'
+
+    # ------------------------------------------------------------------------------------------
+
+  - name: Update an ALB by removing tags - check_mode
+    elb_application_lb:
+      name: '{{ alb_name }}'
+      subnets: '{{ public_subnets }}'
+      security_groups: '{{ sec_group.group_id }}'
+      state: present
+      tags: {}
+    register: alb
+    check_mode: yes
+  - assert:
+      that:
+      - alb is changed
+      - alb.msg is match('Would have updated ALB if not in check mode.')
+
+  - name: Update an ALB by removing tags
+    elb_application_lb:
+      name: '{{ alb_name }}'
+      subnets: '{{ public_subnets }}'
+      security_groups: '{{ sec_group.group_id }}'
+      state: present
+      tags: {}
+    register: alb
+  - assert:
+      that:
+      - alb is changed
+      - not alb.tags
+
+  - name: Update an ALB by removing tags (idempotence) - check_mode
+    elb_application_lb:
+      name: '{{ alb_name }}'
+      subnets: '{{ public_subnets }}'
+      security_groups: '{{ sec_group.group_id }}'
+      state: present
+      tags: {}
+    register: alb
+    check_mode: yes
+  - assert:
+      that:
+      - alb is not changed
+      - alb.msg is match('IN CHECK MODE - no changes to make to ALB specified.')
+
+  - name: Update an ALB by removing tags (idempotence)
+    elb_application_lb:
+      name: '{{ alb_name }}'
+      subnets: '{{ public_subnets }}'
+      security_groups: '{{ sec_group.group_id }}'
+      state: present
+      tags: {}
+    register: alb
+  - assert:
+      that:
+      - alb is not changed
+      - not alb.tags
+
+    # ------------------------------------------------------------------------------------------
+
+  - name: Update an ALB by changing security group - check_mode
+    elb_application_lb:
+      name: '{{ alb_name }}'
+      subnets: '{{ public_subnets }}'
+      security_groups: '{{ sec_group2.group_id }}'
+      state: present
+    register: alb
+    check_mode: yes
+  - assert:
+      that:
+      - alb is changed
+      - alb.msg is match('Would have updated ALB if not in check mode.')
+
+  - name: Update an ALB by changing security group
+    elb_application_lb:
+      name: '{{ alb_name }}'
+      subnets: '{{ public_subnets }}'
+      security_groups: '{{ sec_group2.group_id }}'
+      state: present
+    register: alb
+  - assert:
+      that:
+      - alb is changed
+      - alb.security_groups[0] == sec_group2.group_id
+
+  - name: Update an ALB by changing security group (idempotence) - check_mode
+    elb_application_lb:
+      name: '{{ alb_name }}'
+      subnets: '{{ public_subnets }}'
+      security_groups: '{{ sec_group2.group_id }}'
+      state: present
+    register: alb
+    check_mode: yes
+  - assert:
+      that:
+      - alb is not changed
+      - alb.msg is match('IN CHECK MODE - no changes to make to ALB specified.')
+
+  - name: Update an ALB by changing security group (idempotence)
+    elb_application_lb:
+      name: '{{ alb_name }}'
+      subnets: '{{ public_subnets }}'
+      security_groups: '{{ sec_group2.group_id }}'
+      state: present
+    register: alb
+  - assert:
+      that:
+      - alb is not changed
+      - alb.security_groups[0] == sec_group2.group_id
+
+    # ------------------------------------------------------------------------------------------
+
+  - name: Ensure elb_application_lb_info supports check_mode
+    elb_application_lb_info:
+    register: alb_info
+    check_mode: yes
+  - assert:
+      that:
+      - alb_info.load_balancers | length > 0
+
+  - name: Get ALB application info using no args
+    elb_application_lb_info:
+    register: alb_info
+  - assert:
+      that:
+      - alb_info.load_balancers | length > 0
+
+  - name: Get ALB application info using load balancer arn
+    elb_application_lb_info:
+      load_balancer_arns:
+      - '{{ alb.load_balancer_arn }}'
+    register: alb_info
+  - assert:
+      that:
+      - alb_info.load_balancers[0].security_groups[0] == sec_group2.group_id
+
+  - name: Get ALB application info using load balancer name
+    elb_application_lb_info:
+      names:
+      - '{{ alb.load_balancer_name }}'
+    register: alb_info
+  - assert:
+      that:
+      - alb_info.load_balancers[0].security_groups[0] == sec_group2.group_id
+
+    # ------------------------------------------------------------------------------------------
+
+  - name: Delete an ALB - check_mode
+    elb_application_lb:
+      name: '{{ alb_name }}'
+      state: absent
+    register: alb
+    check_mode: yes
+  - assert:
+      that:
+      - alb is changed
+      - alb.msg is match('Would have deleted ALB if not in check mode.')
+
+  - name: Delete an ALB
+    elb_application_lb:
+      name: '{{ alb_name }}'
+      state: absent
+    register: alb
+  - assert:
+      that:
+      - alb is changed
+
+  - name: Delete an ALB (idempotence) - check_mode
+    elb_application_lb:
+      name: '{{ alb_name }}'
+      state: absent
+    register: alb
+    check_mode: yes
+  - assert:
+      that:
+      - alb is not changed
+      - alb.msg is match('IN CHECK MODE - ALB already absent.')
+
+  - name: Delete an ALB (idempotence)
+    elb_application_lb:
+      name: '{{ alb_name }}'
+      state: absent
+    register: alb
+  - assert:
+      that:
+      - alb is not changed
+
+  # ----- Cleanup ------------------------------------------------------------------------------
+
+  always:
+  - name: Destroy ALB
+    elb_application_lb:
+      name: '{{ alb_name }}'
+      state: absent
+      wait: true
+      wait_timeout: 600
+    ignore_errors: true
+  - name: Destroy target group if it was created
+    elb_target_group:
+      name: '{{ tg_name }}'
+      protocol: http
+      port: 80
+      vpc_id: '{{ vpc_id }}'
+      state: absent
+      wait: true
+      wait_timeout: 600
+    register: remove_tg
+    retries: 5
+    delay: 3
+    until: remove_tg is success
+    when: tg is defined
+    ignore_errors: true
+  - name: Destroy sec groups
+    ec2_group:
+      name: '{{ item }}'
+      description: security group for Ansible ALB integration tests
+      state: absent
+      vpc_id: '{{ vpc_id }}'
+    register: remove_sg
+    retries: 10
+    delay: 5
+    until: remove_sg is success
+    ignore_errors: true
+    with_items:
+    - '{{ resource_prefix }}'
+    - '{{ resource_prefix }}-2'
+
+  - name: Destroy route table
+    ec2_vpc_route_table:
+      vpc_id: '{{ vpc_id }}'
+      route_table_id: '{{ route_table.route_table.route_table_id }}'
+      lookup: id
+      state: absent
+    register: remove_rt
+    retries: 10
+    delay: 5
+    until: remove_rt is success
+    ignore_errors: true
+  - name: Destroy subnets
+    ec2_vpc_subnet:
+      cidr: '{{ item }}'
+      vpc_id: '{{ vpc_id }}'
+      state: absent
+    register: remove_subnet
+    retries: 10
+    delay: 5
+    until: remove_subnet is success
+    with_items:
+    - '{{ private_subnet_cidr_1 }}'
+    - '{{ private_subnet_cidr_2 }}'
+    - '{{ public_subnet_cidr_1 }}'
+    - '{{ public_subnet_cidr_2 }}'
+    ignore_errors: true
+  - name: Destroy internet gateway
+    ec2_vpc_igw:
+      vpc_id: '{{ vpc_id }}'
+      tags:
+        Name: '{{ resource_prefix }}'
+      state: absent
+    register: remove_igw
+    retries: 10
+    delay: 5
+    until: remove_igw is success
+    ignore_errors: true
+  - name: Destroy VPC
+    ec2_vpc_net:
+      cidr_block: '{{ vpc_cidr }}'
+      name: '{{ resource_prefix }}_vpc'
+      state: absent
+    register: remove_vpc
+    retries: 10
+    delay: 5
+    until: remove_vpc is success
+    ignore_errors: true