Repository: ansible/ansible-modules-core

Issue:
Standardise on using security group name instead of id
**Issue Type:**

Bug Report

**Ansible Version:**
1.9.1

**Environment:**
aws cloud modules

**Summary:**

In general I find the mix of using security_group_id some places, and the security group name other places  in aws modules cumbersome. 

ec2_elb_lb need to have the security group specified by group id, while ec2 module is happy with only the group name. 

Problem is that if the security group is already created using ec2_group it fails on the second run, and I can't register the variable and get hold of the security group id. So I either have to delete and recreate the security group, or hard-code the group id in my run.

**Steps To Reproduce:**
Run the following twice - (substitute my variables with meaningful vals for you):

``` yaml
- name: Create the security group for the ELBs
  local_action:
    module: ec2_group
    name: "{{ elb_public_vpc_group.name }}"
    description: "{{ elb_public_vpc_group.desc }}"
    vpc_id: "{{ vpc.vpc_id }}"
    region: "{{ ec2_region }}"
    ec2_access_key: "{{ ec2_access_key }}"
    ec2_secret_key: "{{ ec2_secret_key }}"
    rules: "{{ elb_public_vpc_group.rules }}"
    rules_egress: "{{ elb_public_vpc_group.rules_egress }}"
  register: elb_pub_group

- debug: var=elb_pub_group
```

**Expected Results:**

```
TASK: [vpc | debug var=elb_pub_group] *****************************************
ok: [localhost] => {
    "var": {
        "elb_pub_group": {
            "changed": true,
            "group_id": "sg-95e2baf0",
            "invocation": {
                "module_args": "",
                "module_name": "ec2_group"
            }
        }
    }
}
```

I would expect the module to query or update the group so that the current values can be used later on in the script even if the group is already created.

**Actual Results:**

```
TASK: [vpc | Create the security group for the ELBs] **************************
failed: [localhost -> 127.0.0.1] => {"failed": true, "parsed": false}
Traceback (most recent call last):
  File "/Users/olvesh/.ansible/tmp/ansible-tmp-1430921963.5-88774226046590/ec2_group", line 2192, in <module>
    main()
  File "/Users/olvesh/.ansible/tmp/ansible-tmp-1430921963.5-88774226046590/ec2_group", line 348, in main
    cidr_ip=ip)
  File "/usr/local/lib/python2.7/site-packages/boto/ec2/connection.py", line 3229, in authorize_security_group_egress
    params, verb='POST')
  File "/usr/local/lib/python2.7/site-packages/boto/connection.py", line 1226, in get_status
    raise self.ResponseError(response.status, response.reason, body)
boto.exception.EC2ResponseError: EC2ResponseError: 400 Bad Request
<?xml version="1.0" encoding="UTF-8"?>
<Response><Errors><Error><Code>InvalidPermission.Duplicate</Code><Message>the specified rule "peer: 0.0.0.0/0, ICMP, type: ALL, code: ALL, ALLOW" already exists</Message></Error></Errors><RequestID>efd596d8-219d-4323-9024-c283b9b55f0f</RequestID></Response>
```

**EDIT**
I removed the ICMP egress from the following ruleset and it worked for me, but still feel this is a bug.

``` yaml
elb_public_vpc_group:
  name: "{{ deploy_env }}-public"
  desc: the security group for the dev vpc
  rules:
    - proto: tcp
      from_port: 80
      to_port: 80
      cidr_ip: 0.0.0.0/0
    - proto: tcp
      from_port: 443
      to_port: 443
      cidr_ip: 0.0.0.0/0

  rules_egress:
    - proto: udp
      from_port: 0
      to_port: 65535
      cidr_ip: 0.0.0.0/0
    - proto: tcp
      from_port: 0
      to_port: 65535
      cidr_ip: 0.0.0.0/0
    - proto: icmp
      from_port: -1
      to_port: -1
      cidr_ip: 10.0.0.0/0
```



Discussion:

Resolving patch:
diff --git a/cloud/amazon/ec2_elb_lb.py b/cloud/amazon/ec2_elb_lb.py
--- a/cloud/amazon/ec2_elb_lb.py
+++ b/cloud/amazon/ec2_elb_lb.py
@@ -56,6 +56,12 @@
     require: false
     default: None
     version_added: "1.6"
+  security_group_names:
+    description:
+      - A list of security group names to apply to the elb
+    require: false
+    default: None
+    version_added: "2.0"
   health_check:
     description:
       - An associative array of health check configuration settings (see example)
@@ -68,7 +74,7 @@
     aliases: ['aws_region', 'ec2_region']
   subnets:
     description:
-      - A list of VPC subnets to use when creating ELB. Zones should be empty if using this. 
+      - A list of VPC subnets to use when creating ELB. Zones should be empty if using this.
     required: false
     default: None
     aliases: []
@@ -77,7 +83,7 @@
     description:
       - Purge existing subnet on ELB that are not found in subnets
     required: false
-    default: false  
+    default: false
     version_added: "1.7"
   scheme:
     description:
@@ -147,7 +153,7 @@
     name: "test-vpc"
     scheme: internal
     state: present
-    subnets: 
+    subnets:
       - subnet-abcd1234
       - subnet-1a2b3c4d
     listeners:
@@ -213,7 +219,7 @@
         instance_port: 80
     purge_zones: yes
 
-# Creates a ELB and assigns a list of subnets to it. 
+# Creates a ELB and assigns a list of subnets to it.
 - local_action:
     module: ec2_elb_lb
     state: present
@@ -297,10 +303,10 @@ class ElbManager(object):
     """Handles ELB creation and destruction"""
 
     def __init__(self, module, name, listeners=None, purge_listeners=None,
-                 zones=None, purge_zones=None, security_group_ids=None, 
+                 zones=None, purge_zones=None, security_group_ids=None,
                  health_check=None, subnets=None, purge_subnets=None,
                  scheme="internet-facing", connection_draining_timeout=None,
-                 cross_az_load_balancing=None, 
+                 cross_az_load_balancing=None,
                  stickiness=None, region=None, **aws_connect_params):
 
         self.module = module
@@ -361,7 +367,8 @@ def get_info(self):
         if not check_elb:
             info = {
                 'name': self.name,
-                'status': self.status
+                'status': self.status,
+                'region': self.region
             }
         else:
             try:
@@ -388,7 +395,8 @@ def get_info(self):
                 'instances': [instance.id for instance in check_elb.instances],
                 'out_of_service_count': 0,
                 'in_service_count': 0,
-                'unknown_instance_state_count': 0
+                'unknown_instance_state_count': 0,
+                'region': self.region
             }
 
             # status of instances behind the ELB
@@ -442,7 +450,7 @@ def get_info(self):
                 else:
                     info['cross_az_load_balancing'] = 'no'
 
-            # return stickiness info? 
+            # return stickiness info?
 
         return info
 
@@ -622,7 +630,7 @@ def _set_subnets(self):
                 self._attach_subnets(subnets_to_attach)
             if subnets_to_detach:
                 self._detach_subnets(subnets_to_detach)
-                
+
     def _set_zones(self):
         """Determine which zones need to be enabled or disabled on the ELB"""
         if self.zones:
@@ -727,7 +735,7 @@ def _set_stickiness_policy(self, elb_info, listeners_dict, policy, **policy_attr
         else:
             self._create_policy(policy_attrs['param_value'], policy_attrs['method'], policy[0])
             self.changed = True
-        
+
         self._set_listener_policy(listeners_dict, policy)
 
     def select_stickiness_policy(self):
@@ -794,7 +802,7 @@ def select_stickiness_policy(self):
 
             else:
                 self._set_listener_policy(listeners_dict)
- 
+
     def _get_health_check_target(self):
         """Compose target string from healthcheck parameters"""
         protocol = self.health_check['ping_protocol'].upper()
@@ -816,6 +824,7 @@ def main():
             zones={'default': None, 'required': False, 'type': 'list'},
             purge_zones={'default': False, 'required': False, 'type': 'bool'},
             security_group_ids={'default': None, 'required': False, 'type': 'list'},
+            security_group_names={'default': None, 'required': False, 'type': 'list'},
             health_check={'default': None, 'required': False, 'type': 'dict'},
             subnets={'default': None, 'required': False, 'type': 'list'},
             purge_subnets={'default': False, 'required': False, 'type': 'bool'},
@@ -828,6 +837,7 @@ def main():
 
     module = AnsibleModule(
         argument_spec=argument_spec,
+        mutually_exclusive = [['security_group_ids', 'security_group_names']]
     )
 
     if not HAS_BOTO:
@@ -844,6 +854,7 @@ def main():
     zones = module.params['zones']
     purge_zones = module.params['purge_zones']
     security_group_ids = module.params['security_group_ids']
+    security_group_names = module.params['security_group_names']
     health_check = module.params['health_check']
     subnets = module.params['subnets']
     purge_subnets = module.params['purge_subnets']
@@ -858,6 +869,21 @@ def main():
     if state == 'present' and not (zones or subnets):
         module.fail_json(msg="At least one availability zone or subnet is required for ELB creation")
 
+    if security_group_names:
+        security_group_ids = []
+        try:
+            ec2 = ec2_connect(module)
+            grp_details = ec2.get_all_security_groups()
+
+            for group_name in security_group_names:
+                if isinstance(group_name, basestring):
+                    group_name = [group_name]
+
+                group_id = [ str(grp.id) for grp in grp_details if str(grp.name) in group_name ]
+                security_group_ids.extend(group_id)
+        except boto.exception.NoAuthHandlerFound, e:
+            module.fail_json(msg = str(e))
+
     elb_man = ElbManager(module, name, listeners, purge_listeners, zones,
                          purge_zones, security_group_ids, health_check,
                          subnets, purge_subnets, scheme,


Test patch: