Repository: ansible/ansible

Issue:
instance_role parameter for ec2_instance module doesn't work
<!---
Verify first that your issue/request is not already reported on GitHub.
Also test if the latest release, and devel branch are affected too.
-->

##### ISSUE TYPE
<!--- Pick one below and delete the rest -->
 - Bug Report

##### COMPONENT NAME
<!---
Name of the module, plugin, task or feature
Do not include extra details here, e.g. "vyos_command" not "the network module vyos_command" or the full path
-->
ec2_instance

##### ANSIBLE VERSION
<!--- Paste verbatim output from "ansible --version" between quotes below -->
```
ansible 2.6.0
  config file = /Users/vink/.ansible.cfg
  configured module search path = [u'/Users/vink/.ansible/plugins/modules', u'/usr/share/ansible/plugins/modules']
  ansible python module location = /Library/Python/2.7/site-packages/ansible
  executable location = /usr/local/bin/ansible
  python version = 2.7.10 (default, Jul 15 2017, 17:16:57) [GCC 4.2.1 Compatible Apple LLVM 9.0.0 (clang-900.0.31)]
```
This was updated using "pip install git+https://github.com/ansible/ansible.git@devel" today. 

##### 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).
-->
DISPLAY_SKIPPED_HOSTS(/Users/vink/.ansible.cfg) = True
HOST_KEY_CHECKING(/Users/vink/.ansible.cfg) = False
RETRY_FILES_ENABLED(/Users/vink/.ansible.cfg) = False

##### 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.
-->
OS X High Sierra, but this is probably not platform-specific

##### SUMMARY
<!--- Explain the problem briefly -->
The instance_role parameter for the new ec2_instance module doesn't work. It attempts to call determine_iam_role() with the given parameter, but that expects 2 arguments instead of one. 

##### 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
    - name: Deploy new EC2 instance
      ec2_instance:
          instance_type: "{{instance_type}}"
          image_id: "{{ami_id}}"
          region: "{{region}}"
          vpc_subnet_id: "{{ subnet_id }}"
          instance_role: "{{iam_instance_role_arn}}"
```

<!--- You can also paste gist.github.com links for larger files -->

##### EXPECTED RESULTS
<!--- What did you expect to happen when running the steps above? -->
A newly launched EC2 instance with the correct IAM role attached.

##### ACTUAL RESULTS
<!--- What actually happened? If possible run with extra verbosity (-vvvv) -->

<!--- Paste verbatim command output between quotes below -->
```
Using module file /Library/Python/2.7/site-packages/ansible/modules/cloud/amazon/ec2_instance.py
<127.0.0.1> ESTABLISH LOCAL CONNECTION FOR USER: vink
<127.0.0.1> EXEC /bin/sh -c 'echo ~ && sleep 0'
<127.0.0.1> EXEC /bin/sh -c '( umask 77 && mkdir -p "` echo /Users/vink/.ansible/tmp/ansible-tmp-1521107885.04-209498160937436 `" && echo ansible-tmp-1521107885.04-209498160937436="` echo /Users/vink/.ansible/tmp/ansible-tmp-1521107885.04-209498160937436 `" ) && sleep 0'
<127.0.0.1> PUT /Users/vink/.ansible/tmp/ansible-local-20586FoULbu/tmpYiVFpS TO /Users/vink/.ansible/tmp/ansible-tmp-1521107885.04-209498160937436/ec2_instance.py
<127.0.0.1> EXEC /bin/sh -c 'chmod u+x /Users/vink/.ansible/tmp/ansible-tmp-1521107885.04-209498160937436/ /Users/vink/.ansible/tmp/ansible-tmp-1521107885.04-209498160937436/ec2_instance.py && sleep 0'
<127.0.0.1> EXEC /bin/sh -c '/usr/bin/python /Users/vink/.ansible/tmp/ansible-tmp-1521107885.04-209498160937436/ec2_instance.py && sleep 0'
<127.0.0.1> EXEC /bin/sh -c 'rm -f -r /Users/vink/.ansible/tmp/ansible-tmp-1521107885.04-209498160937436/ > /dev/null 2>&1 && sleep 0'
The full traceback is:
Traceback (most recent call last):
  File "/var/folders/wk/vr0zt0d53ssdsqqjvh5sv3fr0000gn/T/ansible_ejppjJ/ansible_module_ec2_instance.py", line 1443, in <module>
    main()
  File "/var/folders/wk/vr0zt0d53ssdsqqjvh5sv3fr0000gn/T/ansible_ejppjJ/ansible_module_ec2_instance.py", line 1428, in main
    ensure_present(existing_matches=existing_matches, changed=changed, ec2=ec2, state=state)
  File "/var/folders/wk/vr0zt0d53ssdsqqjvh5sv3fr0000gn/T/ansible_ejppjJ/ansible_module_ec2_instance.py", line 1295, in ensure_present
    instance_spec = build_run_instance_spec(module.params)
  File "/var/folders/wk/vr0zt0d53ssdsqqjvh5sv3fr0000gn/T/ansible_ejppjJ/ansible_module_ec2_instance.py", line 976, in build_run_instance_spec
    spec['IamInstanceProfile'] = dict(Arn=determine_iam_role(params.get('iam_profile')))
TypeError: determine_iam_role() takes exactly 2 arguments (1 given)

fatal: [localhost]: FAILED! => {
    "changed": false, 
    "module_stderr": "Traceback (most recent call last):\n  File \"/var/folders/wk/vr0zt0d53ssdsqqjvh5sv3fr0000gn/T/ansible_ejppjJ/ansible_module_ec2_instance.py\", line 1443, in <module>\n    main()\n  File \"/var/folders/wk/vr0zt0d53ssdsqqjvh5sv3fr0000gn/T/ansible_ejppjJ/ansible_module_ec2_instance.py\", line 1428, in main\n    ensure_present(existing_matches=existing_matches, changed=changed, ec2=ec2, state=state)\n  File \"/var/folders/wk/vr0zt0d53ssdsqqjvh5sv3fr0000gn/T/ansible_ejppjJ/ansible_module_ec2_instance.py\", line 1295, in ensure_present\n    instance_spec = build_run_instance_spec(module.params)\n  File \"/var/folders/wk/vr0zt0d53ssdsqqjvh5sv3fr0000gn/T/ansible_ejppjJ/ansible_module_ec2_instance.py\", line 976, in build_run_instance_spec\n    spec['IamInstanceProfile'] = dict(Arn=determine_iam_role(params.get('iam_profile')))\nTypeError: determine_iam_role() takes exactly 2 arguments (1 given)\n", 
    "module_stdout": "", 
    "msg": "MODULE FAILURE", 
    "rc": 1
}
```

I tried the following values for instance_role:
- The full ARN as returned by iam_roles
- The role ID as returned by iam_roles
- A dict containing both, since ec2_instances returns the instance role in that format in its results



Discussion:
Files identified in the description:
* [lib/ansible/modules/cloud/amazon/ec2_instance.py](https://github.com/ansible/ansible/blob/devel/lib/ansible/modules/cloud/amazon/ec2_instance.py)

If these files are inaccurate, please update the `component name` section of the description or use the `!component` bot command.

[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md)
<!--- boilerplate: components_banner --->
cc @ryansb @willthames
[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md)
<!--- boilerplate: notify --->

Resolving patch:
diff --git a/lib/ansible/modules/cloud/amazon/ec2_instance.py b/lib/ansible/modules/cloud/amazon/ec2_instance.py
--- a/lib/ansible/modules/cloud/amazon/ec2_instance.py
+++ b/lib/ansible/modules/cloud/amazon/ec2_instance.py
@@ -994,7 +994,7 @@ def build_run_instance_spec(params, ec2=None):
 
     # IAM profile
     if params.get('instance_role'):
-        spec['IamInstanceProfile'] = dict(Arn=determine_iam_role(params.get('iam_profile')))
+        spec['IamInstanceProfile'] = dict(Arn=determine_iam_role(params.get('instance_role')))
 
     spec['InstanceType'] = params['instance_type']
     return spec
@@ -1267,11 +1267,10 @@ def pretty_instance(i):
     return instance
 
 
-def determine_iam_role(name_or_arn, iam):
+def determine_iam_role(name_or_arn):
     if re.match(r'^arn:aws:iam::\d+:instance-profile/[\w+=/,.@-]+$', name_or_arn):
         return name_or_arn
-    if iam is None:
-        iam = module.client('iam')
+    iam = module.client('iam')
     try:
         role = iam.get_instance_profile(InstanceProfileName=name_or_arn)
         return role['InstanceProfile']['Arn']


Test patch:
diff --git a/test/integration/targets/ec2_instance/files/assume-role-policy.json b/test/integration/targets/ec2_instance/files/assume-role-policy.json
new file mode 100644
--- /dev/null
+++ b/test/integration/targets/ec2_instance/files/assume-role-policy.json
@@ -0,0 +1,13 @@
+{
+  "Version": "2008-10-17",
+  "Statement": [
+    {
+      "Sid": "",
+      "Effect": "Allow",
+      "Principal": {
+        "Service": "ec2.amazonaws.com"
+      },
+      "Action": "sts:AssumeRole"
+    }
+  ]
+}
diff --git a/test/integration/targets/ec2_instance/tasks/iam_instance_role.yml b/test/integration/targets/ec2_instance/tasks/iam_instance_role.yml
new file mode 100644
--- /dev/null
+++ b/test/integration/targets/ec2_instance/tasks/iam_instance_role.yml
@@ -0,0 +1,62 @@
+- name: set connection information for all tasks
+  set_fact:
+    aws_connection_info: &aws_connection_info
+      aws_access_key: "{{ aws_access_key }}"
+      aws_secret_key: "{{ aws_secret_key }}"
+      security_token: "{{ security_token }}"
+      region: "{{ aws_region }}"
+  no_log: true
+
+- block:
+    - name: Create IAM role for test
+      iam_role:
+        name: "{{ resource_prefix }}-test-policy"
+        assume_role_policy_document: "{{ lookup('file','assume-role-policy.json') }}"
+        state: present
+        create_instance_profile: yes
+        managed_policy:
+        - AmazonEC2ContainerServiceRole
+        <<: *aws_connection_info
+      register: iam_role
+
+    - name: Wait for IAM role to be available, otherwise the next step will fail (Invalid IAM Instance Profile name)
+      command: sleep 10
+
+    - name: Make instance with an instance_role
+      ec2_instance:
+        name: "{{ resource_prefix }}-test-default-vpc"
+        image_id: "{{ ec2_ami_image[aws_region] }}"
+        security_groups: "{{ sg.group_id }}"
+        instance_type: t2.micro
+        instance_role: "{{ resource_prefix }}-test-policy"
+        <<: *aws_connection_info
+      register: instance_with_role
+
+    - assert:
+        that:
+          - 'instance_with_role.instances[0].iam_instance_profile.arn == iam_role.arn.replace(":role/", ":instance-profile/")'
+
+  always:
+    - name: Terminate instance
+      ec2:
+        instance_ids: "{{ instance_with_role.instance_ids }}"
+        state: absent
+        <<: *aws_connection_info
+      register: removed
+      until: removed is not failed
+      ignore_errors: yes
+      retries: 10
+
+    - name: Delete IAM role for test
+      iam_role:
+        name: "{{ resource_prefix }}-test-policy"
+        assume_role_policy_document: "{{ lookup('file','assume-role-policy.json') }}"
+        state: absent
+        create_instance_profile: yes
+        managed_policy:
+        - AmazonEC2ContainerServiceRole
+        <<: *aws_connection_info
+      register: removed
+      until: removed is not failed
+      ignore_errors: yes
+      retries: 10
diff --git a/test/integration/targets/ec2_instance/tasks/main.yml b/test/integration/targets/ec2_instance/tasks/main.yml
--- a/test/integration/targets/ec2_instance/tasks/main.yml
+++ b/test/integration/targets/ec2_instance/tasks/main.yml
@@ -94,6 +94,7 @@
     - include_tasks: tasks/external_resource_attach.yml
     - include_tasks: tasks/block_devices.yml
     - include_tasks: tasks/default_vpc_tests.yml
+    - include_tasks: tasks/iam_instance_role.yml
 
 
     # ============================================================