Repository: ansible/ansible

Issue:
ec2_eni module modifying instance to attach different eni than what was requested
##### ISSUE TYPE
<!--- Pick one below and delete the rest: -->
 - Bug Report

##### COMPONENT NAME
<!--- Name of the module/plugin/task/feature -->
ec2_eni

##### ANSIBLE VERSION
<!--- Paste verbatim output from “ansible --version” between quotes below -->
```
$ ansible --version
ansible 2.3.0 @ 00859a4
  config file = ansible.cfg
  configured module search path = Default w/o overrides
```

##### CONFIGURATION
- defaults in ansible.cfg
- uses ec2.py inventory script to get data from AWS (also default configuration in ec2.ini)

##### OS / ENVIRONMENT
- OSX 10.11.6
- Python 2.7.12

##### SUMMARY
ec2_eni module is called with a specific eni to attach to a specific instance.  Each time that it's called, the invocation arguments being passed to the module look correct, but the response from the module indicates that a new ENI was created with the default settings, and attached to the instance selected. 

I can confirm that by running the same command directly with the aws cli, that the selected eni gets attached properly.

I have been able to pretty consistently reproduce this issue within my playbook, and will try to narrow it down below.

##### STEPS TO REPRODUCE

<!--- Paste example playbooks or commands between quotes below -->
```yaml
- debug: 
    msg: "associating {{ansible_ec2_instance_id}} with {{(dest_eni|first).network_interface_id}} in {{ (instance_facts.instances|first).tags.subnet }}" 

- name: associate selected eni to this instance
  connection: local
  ec2_eni:
    attached: yes
    device_index: 1 
    region: "{{ region }}"
    instance_id: "{{ ansible_ec2_instance_id }}"
    eni_id: "{{ (dest_eni|first).network_interface_id }}"
    subnet_id: "{{ (instance_facts.instances|first).tags.subnet }}"
```

##### EXPECTED RESULTS
<!--- What did you expect to happen when running the steps above? -->

In the context of the two steps above, I would have expected the instance to have have the eni selected to be added to the instance selected (see actual results below).

##### ACTUAL RESULTS
<!--- What actually happened? If possible run with extra verbosity (-vvvv) -->

As you can see below, the output of the commands show that instance `i-0669e0aa441a0258b` should be associated with `eni-d14560a9` as in the debug statement, and the module `invocation` arguments.  

However - the output of the module (the `interface` object) is returned saying that instance `i-0669e0aa441a0258b` is incorrectly given a newly created `eni-398fa941` ENI.

<!--- Paste verbatim command output between quotes below -->
```
TASK [allocate_eip : debug] *****************************************************************************************************************
task path: roles/allocate_eip/tasks/main.yml:55
ok: [1.1.1.1] => {
    "msg": "associating i-0669e0aa441a0258b with eni-d14560a9 in subnet-43098b27"
}

TASK [allocate_eip : associate one of the eip's with this instance] *************************************************************************
task path: roles/allocate_eip/tasks/main.yml:61
Using module file /usr/local/Cellar/ansible/HEAD-00859a4_1/libexec/lib/python2.7/site-packages/ansible/modules/cloud/amazon/ec2_eni.py
<1.1.1.1> ESTABLISH LOCAL CONNECTION FOR USER: gaieges
<1.1.1.1> EXEC /bin/sh -c '/usr/bin/python && sleep 0'
Using module file /usr/local/Cellar/ansible/HEAD-00859a4_1/libexec/lib/python2.7/site-packages/ansible/modules/cloud/amazon/ec2_eni.py
<1.1.1.1> ESTABLISH LOCAL CONNECTION FOR USER: gaieges
<1.1.1.1> EXEC /bin/sh -c '/usr/bin/python && sleep 0'
changed: [1.1.1.1] => {
    "changed": true,
    "interface": {
        "attachment": {
            "attach_time": "2016-12-16T19:20:48.000Z",
            "attachment_id": "eni-attach-6d7b2a02",
            "delete_on_termination": false,
            "device_index": 1,
            "instance_id": "i-0669e0aa441a0258b",
            "status": "attached"
        },
        "description": "",
        "groups": {
            "sg-83fc17e4": "ssh_everywhere",
            "sg-bcfc17db": "indexer"
        },
        "id": "eni-398fa941",
        "mac_address": "02:52:8a:05:9b:13",
        "owner_id": "473480",
        "private_ip_address": "10.0.10.11",
        "private_ip_addresses": [
            {
                "primary_address": true,
                "private_ip_address": "10.0.10.11"
            }
        ],
        "source_dest_check": true,
        "status": "in-use",
        "subnet_id": "subnet-43098b27",
        "vpc_id": "vpc-0dd81f69"
    },
    "invocation": {
        "module_args": {
            "attached": true,
            "aws_access_key": null,
            "aws_secret_key": null,
            "delete_on_termination": null,
            "description": null,
            "device_index": 1,
            "ec2_url": null,
            "eni_id": "eni-d14560a9",
            "force_detach": false,
            "instance_id": "i-0669e0aa441a0258b",
            "private_ip_address": null,
            "profile": null,
            "region": "ap-southeast-2",
            "secondary_private_ip_address_count": null,
            "secondary_private_ip_addresses": null,
            "security_groups": [
                "indexer",
                "ssh_everywhere"
            ],
            "security_token": null,
            "source_dest_check": null,
            "state": "present",
            "subnet_id": "subnet-43098b27",
            "validate_certs": true
        },
        "module_name": "ec2_eni"
    }
}
```



Discussion:
cc @wimnat
[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md)
<!--- boilerplate: notify --->
I can confirm this same issue is seen in 2.2.1.0

Resolving patch:
diff --git a/lib/ansible/modules/cloud/amazon/ec2_eni.py b/lib/ansible/modules/cloud/amazon/ec2_eni.py
--- a/lib/ansible/modules/cloud/amazon/ec2_eni.py
+++ b/lib/ansible/modules/cloud/amazon/ec2_eni.py
@@ -426,6 +426,9 @@ def modify_eni(connection, vpc_id, module, eni):
         if attached is True:
             if eni.attachment and eni.attachment.instance_id != instance_id:
                 detach_eni(eni, module)
+                eni.attach(instance_id, device_index)
+                wait_for_eni(eni, "attached")
+                changed = True
             if eni.attachment is None:
                 eni.attach(instance_id, device_index)
                 wait_for_eni(eni, "attached")
@@ -472,10 +475,14 @@ def delete_eni(connection, module):
 
 def detach_eni(eni, module):
 
+    attached = module.params.get("attached")
+
     force_detach = module.params.get("force_detach")
     if eni.attachment is not None:
         eni.detach(force_detach)
         wait_for_eni(eni, "detached")
+        if attached:
+            return
         eni.update()
         module.exit_json(changed=True, interface=get_eni_info(eni))
     else:
@@ -489,6 +496,7 @@ def uniquely_find_eni(connection, module):
     subnet_id = module.params.get('subnet_id')
     instance_id = module.params.get('instance_id')
     device_index = module.params.get('device_index')
+    attached = module.params.get('attached')
 
     try:
         filters = {}
@@ -501,7 +509,7 @@ def uniquely_find_eni(connection, module):
             filters['private-ip-address'] = private_ip_address
             filters['subnet-id'] = subnet_id
 
-        if instance_id and device_index:
+        if not attached and instance_id and device_index:
             filters['attachment.instance-id'] = instance_id
             filters['attachment.device-index'] = device_index
 
@@ -562,11 +570,11 @@ def main():
     module = AnsibleModule(argument_spec=argument_spec,
                            mutually_exclusive=[
                                ['secondary_private_ip_addresses', 'secondary_private_ip_address_count']
-                               ],
+                           ],
                            required_if=([
                                ('state', 'absent', ['eni_id']),
                                ('attached', True, ['instance_id'])
-                               ])
+                           ])
                            )
 
     if not HAS_BOTO:


Test patch:
diff --git a/test/sanity/pep8/legacy-files.txt b/test/sanity/pep8/legacy-files.txt
--- a/test/sanity/pep8/legacy-files.txt
+++ b/test/sanity/pep8/legacy-files.txt
@@ -73,7 +73,6 @@ lib/ansible/modules/cloud/amazon/ec2_eip.py
 lib/ansible/modules/cloud/amazon/ec2_elb.py
 lib/ansible/modules/cloud/amazon/ec2_elb_facts.py
 lib/ansible/modules/cloud/amazon/ec2_elb_lb.py
-lib/ansible/modules/cloud/amazon/ec2_eni.py
 lib/ansible/modules/cloud/amazon/ec2_eni_facts.py
 lib/ansible/modules/cloud/amazon/ec2_key.py
 lib/ansible/modules/cloud/amazon/ec2_lc.py