Repository: localstack/localstack

Issue:
IAM CreateUser tags are not stored
Tags attached to an IAM user are not saved. Create a user with attached tags. The response from the create user doesn't include the tags and when you run a GetUser the tags are not included.
```
aws-sdk-swift % aws --endpoint-url http://localhost:4593 iam create-user --user-name test-user --tags Key=test,Value=hello
```
produces output
```
{
    "User": {
        "Path": "/",
        "UserName": "test-user",
        "UserId": "peldynzrvm2tofi6nmmr",
        "Arn": "arn:aws:iam::000000000000:user/test-user",
        "CreateDate": "2019-10-23T08:46:31.305Z"
    }
}
```
The GetUser call produces exactly the same output
```
aws --endpoint-url http://localhost:4593 iam get-user --user-name test-user
```


I am running localstack as follows
```
docker run -d -p 4593:4593 -e SERVICES='iam' localstack/localstack
```


Discussion:

Resolving patch:
diff --git a/localstack/services/iam/iam_listener.py b/localstack/services/iam/iam_listener.py
--- a/localstack/services/iam/iam_listener.py
+++ b/localstack/services/iam/iam_listener.py
@@ -7,7 +7,6 @@
 
 
 class ProxyListenerIAM(ProxyListener):
-
     def forward_request(self, method, path, data, headers):
         if method == 'POST' and path == '/':
             data = self._reset_account_id(data)
@@ -16,7 +15,6 @@ def forward_request(self, method, path, data, headers):
         return True
 
     def return_response(self, method, path, data, headers, response):
-
         if response.content:
             # fix hardcoded account ID in ARNs returned from this API
             self._fix_account_id(response)
@@ -31,18 +29,21 @@ def _fix_date_format(self, response):
         replacement = r'<CreateDate>\1T\2Z</CreateDate>'
         self._replace(response, pattern, replacement)
 
-    def _fix_account_id(self, response):
+    @staticmethod
+    def _fix_account_id(response):
         return aws_stack.fix_account_id_in_arns(
             response, existing=MOTO_ACCOUNT_ID, replace=TEST_AWS_ACCOUNT_ID)
 
-    def _reset_account_id(self, data):
+    @staticmethod
+    def _reset_account_id(data):
         """ Fix account ID in request payload. All external-facing responses contain our
             predefined account ID (defaults to 000000000000), whereas the backend endpoint
             from moto expects a different hardcoded account ID (123456789012). """
         return aws_stack.fix_account_id_in_arns(
             data, colon_delimiter='%3A', existing=TEST_AWS_ACCOUNT_ID, replace=MOTO_ACCOUNT_ID)
 
-    def _replace(self, response, pattern, replacement):
+    @staticmethod
+    def _replace(response, pattern, replacement):
         content = to_str(response.content)
         response._content = re.sub(pattern, replacement, content)
 
diff --git a/localstack/services/iam/iam_starter.py b/localstack/services/iam/iam_starter.py
--- a/localstack/services/iam/iam_starter.py
+++ b/localstack/services/iam/iam_starter.py
@@ -1,23 +1,82 @@
+import uuid
 from moto.iam.responses import IamResponse, GENERIC_EMPTY_TEMPLATE
-from moto.iam.models import iam_backend as moto_iam_backend
+from moto.iam.models import iam_backend as moto_iam_backend, User
 from localstack import config
 from localstack.constants import DEFAULT_PORT_IAM_BACKEND
 from localstack.services.infra import start_moto_server
 
 
-def patch_moto():
-    def delete_policy(self):
+def apply_patches():
+    def iam_response_create_user(self):
+        user = moto_iam_backend.create_user(
+            self._get_param('UserName'),
+            self._get_param('Path'),
+            self._get_multi_param('Tags.member')
+        )
+
+        template = self.response_template(USER_RESPONSE_TEMPLATE)
+        return template.render(
+            action='Create',
+            user=user,
+            request_id=str(uuid.uuid4())
+        )
+
+    IamResponse.create_user = iam_response_create_user
+
+    def iam_response_get_user(self):
+        user_name = self._get_param('UserName')
+        if not user_name:
+            access_key_id = self.get_current_user()
+            user = moto_iam_backend.get_user_from_access_key_id(access_key_id)
+            if user is None:
+                user = User('default_user')
+        else:
+            user = moto_iam_backend.get_user(user_name)
+
+        template = self.response_template(USER_RESPONSE_TEMPLATE)
+        return template.render(
+            action='Get',
+            user=user,
+            request_id=str(uuid.uuid4())
+        )
+
+    IamResponse.get_user = iam_response_get_user
+
+    def iam_response_delete_policy(self):
         policy_arn = self._get_param('PolicyArn')
         moto_iam_backend.managed_policies.pop(policy_arn, None)
         template = self.response_template(GENERIC_EMPTY_TEMPLATE)
         return template.render(name='DeletePolicyResponse')
 
     if not hasattr(IamResponse, 'delete_policy'):
-        IamResponse.delete_policy = delete_policy
+        IamResponse.delete_policy = iam_response_delete_policy
 
 
 def start_iam(port=None, asynchronous=False, update_listener=None):
     port = port or config.PORT_IAM
-    patch_moto()
+
+    apply_patches()
     return start_moto_server('iam', port, name='IAM', asynchronous=asynchronous,
-        backend_port=DEFAULT_PORT_IAM_BACKEND, update_listener=update_listener)
+                             backend_port=DEFAULT_PORT_IAM_BACKEND, update_listener=update_listener)
+
+
+USER_RESPONSE_TEMPLATE = """<{{ action }}UserResponse>
+   <{{ action }}UserResult>
+      <User>
+         <Path>{{ user.path }}</Path>
+         <UserName>{{ user.name }}</UserName>
+         <UserId>{{ user.id }}</UserId>
+         <Arn>{{ user.arn }}</Arn>
+         <CreateDate>{{ user.created_iso_8601 }}</CreateDate>
+         <Tags>
+            {% for tag in user.tags %}<Tag>
+                <Key>{{ tag.Key }}</Key>
+                <Value>{{ tag.Value }}</Value>
+            </Tag>{% endfor %}
+         </Tags>
+     </User>
+   </{{ action }}UserResult>
+   <ResponseMetadata>
+      <RequestId>{{request_id}}</RequestId>
+   </ResponseMetadata>
+</{{ action }}UserResponse>"""


Test patch:
diff --git a/tests/integration/test_iam.py b/tests/integration/test_iam.py
--- a/tests/integration/test_iam.py
+++ b/tests/integration/test_iam.py
@@ -106,3 +106,28 @@ def test_recreate_iam_role(self):
         self.iam_client.delete_role(
             RoleName=role_name
         )
+
+    def test_create_user_with_tags(self):
+        user_name = 'user-role-{}'.format(short_uid())
+
+        rs = self.iam_client.create_user(
+            UserName=user_name,
+            Tags=[
+                {'Key': 'env', 'Value': 'production'}
+            ]
+        )
+
+        self.assertIn('Tags', rs['User'])
+        self.assertEqual(rs['User']['Tags'][0]['Key'], 'env')
+
+        rs = self.iam_client.get_user(
+            UserName=user_name
+        )
+
+        self.assertIn('Tags', rs['User'])
+        self.assertEqual(rs['User']['Tags'][0]['Value'], 'production')
+
+        # clean up
+        self.iam_client.delete_user(
+            UserName=user_name
+        )