Repository: ansible/ansible

Issue:
Feature Idea: Add an AWS NLB Cloud Module
<!---
Verify first that your issue/request is not already reported on GitHub.
Also test if the latest release, and master branch are affected too.
-->

##### ISSUE TYPE
<!--- Pick one below and delete the rest: -->
 - Feature Idea

##### COMPONENT NAME
<!--- Name of the module/plugin/task/feature -->

AWS NLB Cloud Module

##### ANSIBLE VERSION
<!--- Paste verbatim output from "ansible --version" between quotes below -->
```
 ansible --version
ansible 2.4.0.0
  config file = /etc/ansible/ansible.cfg
  configured module search path = [u'/root/.ansible/plugins/modules', u'/usr/share/ansible/plugins/modules']
  ansible python module location = /usr/lib/python2.7/site-packages/ansible
  executable location = /bin/ansible
  python version = 2.7.5 (default, Aug  2 2016, 04:20:16) [GCC 4.8.5 20150623 (Red Hat 4.8.5-4)]
```

##### CONFIGURATION
<!---
If using Ansible 2.4 or above, paste the results of "ansible-config dump --only-changed"

Otherwise, mention any settings you have changed/added/removed in ansible.cfg
(or using the ANSIBLE_* environment variables).

-->

##### OS / ENVIRONMENT
<!---
Mention the OS you are running Ansible from, and the OS you are
managing, or say "N/A" for anything that is not platform-specific.
Also mention the specific version of what you are trying to control,
e.g. if this is a network bug the version of firmware on the network device.
-->
N/A
##### SUMMARY

AWS just released a new NLB (Network Load Balancer)
<!--- Explain the problem briefly -->

##### STEPS TO REPRODUCE
<!---
For bugs, show exactly how to reproduce the problem, using a minimal test-case.
For new features, show how the feature would be used.
-->

<!--- Paste example playbooks or commands between quotes below -->
```yaml

```

<!--- You can also paste gist.github.com links for larger files -->

##### EXPECTED RESULTS
<!--- What did you expect to happen when running the steps above? -->

##### ACTUAL RESULTS
<!--- What actually happened? If possible run with extra verbosity (-vvvv) -->

<!--- Paste verbatim command output between quotes below -->
```

```



Discussion:
Yep yep!

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
@@ -100,12 +100,20 @@
     description:
       - A dictionary of one or more tags to assign to the load balancer.
     required: false
+  type:
+    description:
+      - Type of Load Balaner, Application or Network Load Balancer.
+    required: false
+    default: application
+    choices: ['application', 'network']
+    version_added: "2.5"
 extends_documentation_fragment:
     - aws
     - ec2
 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.
+  - Security Groups is not a valid parameter for Network Load Balancer.
 '''
 
 EXAMPLES = '''
@@ -185,6 +193,21 @@
                 Type: forward
     state: present
 
+# Create an NLB with listeners
+- elb_application_lb:
+    name: myelb
+    subnets:
+      - subnet-400d543b
+      - subnet-b57b7edc
+    listeners:
+      - Protocol: TCP # Required. The protocol can only be TCP for Network Load Balancer.
+        Port: 80 # Required. The port on which the load balancer is listening.
+        DefaultActions:
+          - Type: forward # Required. Only 'forward' is accepted at this time
+            TargetGroupName: wp-testing
+    type: network
+    state: present
+
 # Remove an ELB
 - elb_application_lb:
     name: myelb
@@ -797,14 +820,20 @@ def create_or_update_elb(connection, connection_ec2, module):
     params = dict()
     params['Name'] = module.params.get("name")
     params['Subnets'] = module.params.get("subnets")
-    try:
-        params['SecurityGroups'] = get_ec2_security_group_ids_from_names(module.params.get('security_groups'), connection_ec2, boto3=True)
-    except ValueError as e:
-        module.fail_json(msg=str(e), exception=traceback.format_exc())
-    except ClientError as e:
-        module.fail_json(msg=e.message, exception=traceback.format_exc(), **camel_dict_to_snake_dict(e.response))
-    except NoCredentialsError as e:
-        module.fail_json(msg="AWS authentication problem. " + e.message, exception=traceback.format_exc())
+    params['Type'] = module.params.get("type")
+
+    if params['Type'] == 'application':
+        is_alb = True
+
+    if is_alb:
+        try:
+            params['SecurityGroups'] = get_ec2_security_group_ids_from_names(module.params.get('security_groups'), connection_ec2, boto3=True)
+        except ValueError as e:
+            module.fail_json(msg=str(e), exception=traceback.format_exc())
+        except ClientError as e:
+            module.fail_json(msg=e.message, exception=traceback.format_exc(), **camel_dict_to_snake_dict(e.response))
+        except NoCredentialsError as e:
+            module.fail_json(msg="AWS authentication problem. " + e.message, exception=traceback.format_exc())
 
     params['Scheme'] = module.params.get("scheme")
     if module.params.get("tags"):
@@ -831,12 +860,13 @@ def create_or_update_elb(connection, connection_ec2, module):
             changed = True
 
         # Security Groups
-        if set(elb['SecurityGroups']) != set(params['SecurityGroups']):
-            try:
-                connection.set_security_groups(LoadBalancerArn=elb['LoadBalancerArn'], SecurityGroups=params['SecurityGroups'])
-            except ClientError as e:
-                module.fail_json(msg=e.message, exception=traceback.format_exc(), **camel_dict_to_snake_dict(e.response))
-            changed = True
+        if is_alb:
+            if set(elb['SecurityGroups']) != set(params['SecurityGroups']):
+                try:
+                    connection.set_security_groups(LoadBalancerArn=elb['LoadBalancerArn'], SecurityGroups=params['SecurityGroups'])
+                except ClientError as e:
+                    module.fail_json(msg=e.message, exception=traceback.format_exc(), **camel_dict_to_snake_dict(e.response))
+                changed = True
 
         # Tags - only need to play with tags if tags parameter has been set to something
         if module.params.get("tags"):
@@ -880,18 +910,19 @@ def create_or_update_elb(connection, connection_ec2, module):
     # Get current attributes
     current_elb_attributes = get_elb_attributes(connection, module, elb['LoadBalancerArn'])
 
-    if access_logs_enabled and current_elb_attributes['access_logs_s3_enabled'] != "true":
-        update_attributes.append({'Key': 'access_logs.s3.enabled', 'Value': "true"})
-    if not access_logs_enabled and current_elb_attributes['access_logs_s3_enabled'] != "false":
-        update_attributes.append({'Key': 'access_logs.s3.enabled', 'Value': 'false'})
-    if access_logs_s3_bucket is not None and access_logs_s3_bucket != current_elb_attributes['access_logs_s3_bucket']:
-        update_attributes.append({'Key': 'access_logs.s3.bucket', 'Value': access_logs_s3_bucket})
-    if access_logs_s3_prefix is not None and access_logs_s3_prefix != current_elb_attributes['access_logs_s3_prefix']:
-        update_attributes.append({'Key': 'access_logs.s3.prefix', 'Value': access_logs_s3_prefix})
-    if deletion_protection and current_elb_attributes['deletion_protection_enabled'] != "true":
-        update_attributes.append({'Key': 'deletion_protection.enabled', 'Value': "true"})
-    if not deletion_protection and current_elb_attributes['deletion_protection_enabled'] != "false":
-        update_attributes.append({'Key': 'deletion_protection.enabled', 'Value': "false"})
+    if is_alb:
+        if access_logs_enabled and current_elb_attributes['access_logs_s3_enabled'] != "true":
+            update_attributes.append({'Key': 'access_logs.s3.enabled', 'Value': "true"})
+        if not access_logs_enabled and current_elb_attributes['access_logs_s3_enabled'] != "false":
+            update_attributes.append({'Key': 'access_logs.s3.enabled', 'Value': 'false'})
+        if access_logs_s3_bucket is not None and access_logs_s3_bucket != current_elb_attributes['access_logs_s3_bucket']:
+            update_attributes.append({'Key': 'access_logs.s3.bucket', 'Value': access_logs_s3_bucket})
+        if access_logs_s3_prefix is not None and access_logs_s3_prefix != current_elb_attributes['access_logs_s3_prefix']:
+            update_attributes.append({'Key': 'access_logs.s3.prefix', 'Value': access_logs_s3_prefix})
+        if deletion_protection and current_elb_attributes['deletion_protection_enabled'] != "true":
+            update_attributes.append({'Key': 'deletion_protection.enabled', 'Value': "true"})
+        if not deletion_protection and current_elb_attributes['deletion_protection_enabled'] != "false":
+            update_attributes.append({'Key': 'deletion_protection.enabled', 'Value': "false"})
     if idle_timeout is not None and str(idle_timeout) != current_elb_attributes['idle_timeout_timeout_seconds']:
         update_attributes.append({'Key': 'idle_timeout.timeout_seconds', 'Value': str(idle_timeout)})
 
@@ -975,6 +1006,7 @@ def main():
             scheme=dict(default='internet-facing', choices=['internet-facing', 'internal']),
             state=dict(choices=['present', 'absent'], type='str'),
             tags=dict(default={}, type='dict'),
+            type=dict(default='application', type='str', choices=['application', 'network']),
             wait_timeout=dict(type='int'),
             wait=dict(type='bool')
         )
@@ -982,7 +1014,7 @@ def main():
 
     module = AnsibleModule(argument_spec=argument_spec,
                            required_if=[
-                               ('state', 'present', ['subnets', 'security_groups'])
+                               ('state', 'present', ['subnets'])
                            ],
                            required_together=(
                                ['access_logs_enabled', 'access_logs_s3_bucket', 'access_logs_s3_prefix']


Test patch: