Repository: getmoto/moto

Issue:
IAM client's delete_role action not implemented
Tried to write a test for a method which calls the [`delete_role()` method](http://boto3.readthedocs.io/en/latest/reference/services/iam.html#IAM.Client.delete_role) on the IAM client but I get this error message: `NotImplementedError: The delete_role action has not been implemented`.

## Backtrace

```python
venv/lib/python3.6/site-packages/botocore/client.py:253: in _api_call
    return self._make_api_call(operation_name, kwargs)
venv/lib/python3.6/site-packages/botocore/client.py:544: in _make_api_call
    operation_model, request_dict)
venv/lib/python3.6/site-packages/botocore/endpoint.py:141: in make_request
    return self._send_request(request_dict, operation_model)
venv/lib/python3.6/site-packages/botocore/endpoint.py:170: in _send_request
    success_response, exception):
venv/lib/python3.6/site-packages/botocore/endpoint.py:249: in _needs_retry
    caught_exception=caught_exception, request_dict=request_dict)
venv/lib/python3.6/site-packages/botocore/hooks.py:227: in emit
    return self._emit(event_name, kwargs)
venv/lib/python3.6/site-packages/botocore/hooks.py:210: in _emit
    response = handler(**kwargs)
venv/lib/python3.6/site-packages/botocore/retryhandler.py:183: in __call__
    if self._checker(attempts, response, caught_exception):
venv/lib/python3.6/site-packages/botocore/retryhandler.py:251: in __call__
    caught_exception)
venv/lib/python3.6/site-packages/botocore/retryhandler.py:269: in _should_retry
    return self._checker(attempt_number, response, caught_exception)
venv/lib/python3.6/site-packages/botocore/retryhandler.py:317: in __call__
    caught_exception)
venv/lib/python3.6/site-packages/botocore/retryhandler.py:223: in __call__
    attempt_number, caught_exception)
venv/lib/python3.6/site-packages/botocore/retryhandler.py:359: in _check_caught_exception
    raise caught_exception
venv/lib/python3.6/site-packages/botocore/endpoint.py:204: in _get_response
    proxies=self.proxies, timeout=self.timeout)
venv/lib/python3.6/site-packages/botocore/vendored/requests/sessions.py:573: in send
    r = adapter.send(request, **kwargs)
venv/lib/python3.6/site-packages/moto/packages/responses/responses.py:300: in unbound_on_send
    return self._on_request(adapter, request, *a, **kwargs)
venv/lib/python3.6/site-packages/moto/packages/responses/responses.py:255: in _on_request
    status, r_headers, body = match['callback'](request)
venv/lib/python3.6/site-packages/moto/core/utils.py:164: in __call__
    result = self.callback(request, request.url, request.headers)
venv/lib/python3.6/site-packages/moto/core/responses.py:111: in dispatch
    return cls()._dispatch(*args, **kwargs)
venv/lib/python3.6/site-packages/moto/core/responses.py:179: in _dispatch
    return self.call_action()
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <moto.iam.responses.IamResponse object at 0x10b41c128>

    def call_action(self):
        headers = self.response_headers
        action = self.querystring.get('Action', [""])[0]
        if not action:  # Some services use a header for the action
            # Headers are case-insensitive. Probably a better way to do this.
            match = self.headers.get(
                'x-amz-target') or self.headers.get('X-Amz-Target')
            if match:
                action = match.split(".")[-1]
    
        action = camelcase_to_underscores(action)
        method_names = method_names_from_class(self.__class__)
        if action in method_names:
            method = getattr(self, action)
            try:
                response = method()
            except HTTPException as http_error:
                response = http_error.description, dict(status=http_error.code)
            if isinstance(response, six.string_types):
                return 200, headers, response
            else:
                body, new_headers = response
                status = new_headers.get('status', 200)
                headers.update(new_headers)
                # Cast status to string
                if "status" in headers:
                    headers['status'] = str(headers['status'])
                return status, headers, body
    
        raise NotImplementedError(
>           "The {0} action has not been implemented".format(action))
E       NotImplementedError: The delete_role action has not been implemented

venv/lib/python3.6/site-packages/moto/core/responses.py:211: NotImplementedError
```

## How to reproduce
1) Write a method which tries to delete an IAM role:

```python
import boto3
from moto import mock_iam


@mock_iam
def test_delete_role():
    client = boto3.client("iam")
    client.delete_role(RoleName="test_role")
```

2) Run it will fail with that error


Discussion:

Resolving patch:
diff --git a/moto/iam/models.py b/moto/iam/models.py
--- a/moto/iam/models.py
+++ b/moto/iam/models.py
@@ -569,6 +569,13 @@ def get_role(self, role_name):
                 return role
         raise IAMNotFoundException("Role {0} not found".format(role_name))
 
+    def delete_role(self, role_name):
+        for role in self.get_roles():
+            if role.name == role_name:
+                del self.roles[role.id]
+                return
+        raise IAMNotFoundException("Role {0} not found".format(role_name))
+
     def get_roles(self):
         return self.roles.values()
 
diff --git a/moto/iam/responses.py b/moto/iam/responses.py
--- a/moto/iam/responses.py
+++ b/moto/iam/responses.py
@@ -62,6 +62,12 @@ def get_role(self):
         template = self.response_template(GET_ROLE_TEMPLATE)
         return template.render(role=role)
 
+    def delete_role(self):
+        role_name = self._get_param('RoleName')
+        iam_backend.delete_role(role_name)
+        template = self.response_template(GENERIC_EMPTY_TEMPLATE)
+        return template.render(name="DeleteRoleResponse")
+
     def list_role_policies(self):
         role_name = self._get_param('RoleName')
         role_policies_names = iam_backend.list_role_policies(role_name)


Test patch:
diff --git a/tests/test_iam/test_iam.py b/tests/test_iam/test_iam.py
--- a/tests/test_iam/test_iam.py
+++ b/tests/test_iam/test_iam.py
@@ -8,7 +8,7 @@
 from botocore.exceptions import ClientError
 from moto import mock_iam, mock_iam_deprecated
 from moto.iam.models import aws_managed_policies
-from nose.tools import assert_raises, assert_equals, assert_not_equals
+from nose.tools import assert_raises, assert_equals
 from nose.tools import raises
 
 from tests.helpers import requires_boto_gte
@@ -114,6 +114,23 @@ def test_remove_role_from_instance_profile():
     dict(profile.roles).should.be.empty
 
 
+@mock_iam()
+def test_delete_role():
+    conn = boto3.client('iam', region_name='us-east-1')
+
+    with assert_raises(ClientError):
+        conn.delete_role(RoleName="my-role")
+
+    conn.create_role(RoleName="my-role", AssumeRolePolicyDocument="some policy", Path="/my-path/")
+    role = conn.get_role(RoleName="my-role")
+    role.get('Role').get('Arn').should.equal('arn:aws:iam::123456789012:role/my-path/my-role')
+
+    conn.delete_role(RoleName="my-role")
+
+    with assert_raises(ClientError):
+        conn.get_role(RoleName="my-role")
+
+
 @mock_iam_deprecated()
 def test_list_instance_profiles():
     conn = boto.connect_iam()