Repository: ansible/ansible

Issue:
elb_application_lb cannot append rules only create/replace
<!---
Verify first that your issue/request is not already reported on GitHub.
THIS FORM WILL BE READ BY A MACHINE, COMPLETE ALL SECTIONS AS DESCRIBED.
Also test if the latest release, and devel branch are affected too.
ALWAYS add information AFTER (OUTSIDE) these html comments.
Otherwise it may end up being automatically closed by our bot. -->

##### SUMMARY
elb_application_lb module does not allow for the modification of rules without replacing existing rules.

##### ISSUE TYPE
 - Bug Report

##### COMPONENT NAME
elb_application_lb

##### ANSIBLE VERSION
```
ansible 2.7.0.dev0 (detached HEAD f6160c675d) last updated 2018/07/20 18:28:16 (GMT +000)
  config file = /home/vagrant/.ansible.cfg
  configured module search path = [u'/home/vagrant/.ansible/plugins/modules', u'/usr/share/ansible/plugins/modules']
  ansible python module location = /home/vagrant/ansible/lib/ansible
  executable location = /home/vagrant/ansible/bin/ansible
  python version = 2.7.15 (default, May  9 2018, 11:32:33) [GCC 7.3.1 20180130 (Red Hat 7.3.1-2)]
```

##### CONFIGURATION
DEFAULT_LOG_PATH(/home/vagrant/.ansible.cfg) = /home/vagrant/ansible.log

##### OS / ENVIRONMENT
N/A

##### STEPS TO REPRODUCE
Create an elb with rules then attempt to modify those rules. All rules that are not specified in the new task will be purged.


```yaml
  - name: add a rule to the listener
    elb_application_lb:
      name: "{{ alb_name }}"
      subnets: "{{ alb_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
      <<: *aws_connection_info
    register: alb

  - assert:
      that:
        - alb.changed
        - alb.listeners[0].rules|length == 2

  - name: test a rule can be added and other rules will not be removed
    elb_application_lb:
      name: "{{ alb_name }}"
      subnets: "{{ alb_subnets }}"
      security_groups: "{{ sec_group.group_id }}"
      state: present
      purge_listeners: no
      listeners:
        - Protocol: HTTP
          Port: 80
          DefaultActions:
            - Type: forward
              TargetGroupName: "{{ tg_name }}"
          Rules:
            - Conditions:
                - Field: path-pattern
                  Values:
                    - '/new'
              Priority: '2'
              Actions:
                - TargetGroupName: "{{ tg_name }}"
                  Type: forward
      <<: *aws_connection_info
    register: alb

  - assert:
      that:
        - alb.changed
        - alb.listeners[0].rules|length == 3
```

##### EXPECTED RESULTS
When purge listeners is set to no I expected that the existing listeners and rules would stay in place.
##### ACTUAL RESULTS
Existing rules were purged and replaced.



Discussion:

Resolving patch:
diff --git a/lib/ansible/modules/cloud/amazon/elb_application_lb.py b/lib/ansible/modules/cloud/amazon/elb_application_lb.py
--- a/lib/ansible/modules/cloud/amazon/elb_application_lb.py
+++ b/lib/ansible/modules/cloud/amazon/elb_application_lb.py
@@ -119,6 +119,12 @@
     description:
       - The time in seconds to use in conjunction with I(wait).
     version_added: 2.6
+  purge_rules:
+    description:
+      - When set to no, keep the existing load balancer rules in place. Will modify and add, but will not delete.
+    default: yes
+    type: bool
+    version_added: 2.7
 extends_documentation_fragment:
     - aws
     - ec2
@@ -444,10 +450,11 @@ def create_or_update_elb(elb_obj):
             rules_to_add, rules_to_modify, rules_to_delete = rules_obj.compare_rules()
 
             # Delete rules
-            for rule in rules_to_delete:
-                rule_obj = ELBListenerRule(elb_obj.connection, elb_obj.module, {'RuleArn': rule}, rules_obj.listener_arn)
-                rule_obj.delete()
-                elb_obj.changed = True
+            if elb_obj.module.params['purge_rules']:
+                for rule in rules_to_delete:
+                    rule_obj = ELBListenerRule(elb_obj.connection, elb_obj.module, {'RuleArn': rule}, rules_obj.listener_arn)
+                    rule_obj.delete()
+                    elb_obj.changed = True
 
             # Add rules
             for rule in rules_to_add:
@@ -524,7 +531,8 @@ def main():
             state=dict(choices=['present', 'absent'], type='str'),
             tags=dict(type='dict'),
             wait_timeout=dict(type='int'),
-            wait=dict(default=False, type='bool')
+            wait=dict(default=False, type='bool'),
+            purge_rules=dict(default=True, type='bool')
         )
     )
 


Test patch:
diff --git a/test/integration/targets/elb_application_lb/tasks/test_modifying_alb_listeners.yml b/test/integration/targets/elb_application_lb/tasks/test_modifying_alb_listeners.yml
--- a/test/integration/targets/elb_application_lb/tasks/test_modifying_alb_listeners.yml
+++ b/test/integration/targets/elb_application_lb/tasks/test_modifying_alb_listeners.yml
@@ -88,6 +88,36 @@
         - not alb.changed
         - alb.listeners[0].rules|length == 2
 
+  - name: test a rule can be added and other rules will not be removed when purge_rules is no.
+    elb_application_lb:
+      name: "{{ alb_name }}"
+      subnets: "{{ alb_subnets }}"
+      security_groups: "{{ sec_group.group_id }}"
+      state: present
+      purge_rules: no
+      listeners:
+        - Protocol: HTTP
+          Port: 80
+          DefaultActions:
+            - Type: forward
+              TargetGroupName: "{{ tg_name }}"
+          Rules:
+            - Conditions:
+                - Field: path-pattern
+                  Values:
+                    - '/new'
+              Priority: '2'
+              Actions:
+                - TargetGroupName: "{{ tg_name }}"
+                  Type: forward
+      <<: *aws_connection_info
+    register: alb
+
+  - assert:
+      that:
+        - alb.changed
+        - alb.listeners[0].rules|length == 3
+
   - name: remove the rule
     elb_application_lb:
       name: "{{ alb_name }}"