Repository: localstack/localstack

Issue:
Invalid type for parameter tags - type: <class 'list'>, valid types: <class 'dict'>
Hey guys!
I'm having a problem when running a cloud formation script on local stack. Can't find a solution anywhere, and tried a lot different approaches, but still could not make it work.
Is there a different way to declare this declare this values? (Trying to add SQS)

```
2019-11-05T14:38:26:WARNING:localstack.utils.cloudformation.template_deployer: Error calling <bound method ClientCreator._create_api_method.<locals>._api_call of <botocore.client.SQS object at 0x7f3d9ab15a58>> with params: {'QueueName': 'A-DLQ', 'Attributes': {'MessageRetentionPeriod': '1209600'}, 'tags': [{'Key': 'Name', 'Value': 'A-DLQ'}, {'Key': 'Cluster', 'Value': 'A'}]} for resource: {'Type': 'AWS::SQS::Queue', 'Properties': {'QueueName': 'A-DLQ', 'MessageRetentionPeriod': 1209600, 'Tags': [{'Key': 'Name', 'Value': 'A-DLQ'}, {'Key': 'Cluster', 'Value': 'AEvents'}]}}
localstack_1  | 2019-11-05T14:38:26:ERROR:localstack.services.cloudformation.cloudformation_starter: Unable to parse and create resource "DLQ": Parameter validation failed:
localstack_1  | Invalid type for parameter tags, value: [{'Key': 'Name', 'Value': 'A-DLQ'}, {'Key': 'Cluster', 'Value': 'A'}], type: <class 'list'>, valid types: <class 'dict'> Traceback (most recent call last):
localstack_1  |   File "/opt/code/localstack/localstack/services/cloudformation/cloudformation_starter.py", line 149, in parse_and_create_resource
localstack_1  |     return _parse_and_create_resource(logical_id, resource_json, resources_map, region_name)
localstack_1  |   File "/opt/code/localstack/localstack/services/cloudformation/cloudformation_starter.py", line 208, in _parse_and_create_resource
localstack_1  |     result = deploy_func(logical_id, resource_wrapped, stack_name=stack_name)
localstack_1  |   File "/opt/code/localstack/localstack/utils/cloudformation/template_deployer.py", line 647, in deploy_resource
localstack_1  |     raise e
localstack_1  |   File "/opt/code/localstack/localstack/utils/cloudformation/template_deployer.py", line 644, in deploy_resource
localstack_1  |     result = function(**params)
localstack_1  |   File "/opt/code/localstack/.venv/lib/python3.6/site-packages/botocore/client.py", line 357, in _api_call
localstack_1  |     return self._make_api_call(operation_name, kwargs)
localstack_1  |   File "/opt/code/localstack/.venv/lib/python3.6/site-packages/botocore/client.py", line 634, in _make_api_call
localstack_1  |     api_params, operation_model, context=request_context)
localstack_1  |   File "/opt/code/localstack/.venv/lib/python3.6/site-packages/botocore/client.py", line 682, in _convert_to_request_dict
localstack_1  |     api_params, operation_model)
localstack_1  |   File "/opt/code/localstack/.venv/lib/python3.6/site-packages/botocore/validate.py", line 297, in serialize_to_request
localstack_1  |     raise ParamValidationError(report=report.generate_report())
localstack_1  | botocore.exceptions.ParamValidationError: Parameter validation failed:
localstack_1  | Invalid type for parameter tags, value: [{'Key': 'Name', 'Value': 'A-DLQ'}, {'Key': 'Cluster', 'Value': 'AEvents'}], type: <class 'list'>, valid types: <class 'dict'>
localstack_1  |
```

Thanks everyone for any feedback!


Discussion:
Any update?
Thanks for reporting @hikarisakamoto @nakedcity . Has this issue been fixed in the meantime? If not, then can you please provide a small example (CloudFormation config) that helps reproduce this issue? Thanks!

Resolving patch:
diff --git a/localstack/services/infra.py b/localstack/services/infra.py
--- a/localstack/services/infra.py
+++ b/localstack/services/infra.py
@@ -237,10 +237,10 @@ def signal_handler(signal, frame):
     SIGNAL_HANDLERS_SETUP = True
 
 
-def do_run(cmd, asynchronous, print_output=False, env_vars={}):
+def do_run(cmd, asynchronous, print_output=None, env_vars={}):
     sys.stdout.flush()
     if asynchronous:
-        if is_debug():
+        if is_debug() and print_output is None:
             print_output = True
         outfile = subprocess.PIPE if print_output else None
         t = ShellCommandThread(cmd, outfile=outfile, env_vars=env_vars)
diff --git a/localstack/utils/cloudformation/template_deployer.py b/localstack/utils/cloudformation/template_deployer.py
--- a/localstack/utils/cloudformation/template_deployer.py
+++ b/localstack/utils/cloudformation/template_deployer.py
@@ -61,6 +61,17 @@ def do_rename(params, **kwargs):
     return do_rename
 
 
+def params_list_to_dict(param_name, key_attr_name, value_attr_name):
+    def do_replace(params, **kwargs):
+        result = {}
+        for entry in params.get(param_name, []):
+            key = entry[key_attr_name]
+            value = entry[value_attr_name]
+            result[key] = value
+        return result
+    return do_replace
+
+
 def get_nested_stack_name(params, **kwargs):
     stack_name = kwargs.get('stack_name', 'stack')
     return '%s-%s' % (stack_name, common.short_uid())
@@ -149,7 +160,7 @@ def replace(params, **kwargs):
                     ['DelaySeconds', 'MaximumMessageSize', 'MessageRetentionPeriod',
                      'VisibilityTimeout', 'RedrivePolicy']
                 ),
-                'tags': 'Tags'
+                'tags': params_list_to_dict('Tags', 'Key', 'Value')
             }
         },
         'delete': {


Test patch:
diff --git a/tests/integration/templates/template1.yaml b/tests/integration/templates/template1.yaml
--- a/tests/integration/templates/template1.yaml
+++ b/tests/integration/templates/template1.yaml
@@ -14,6 +14,11 @@ Resources:
     Type: AWS::SQS::Queue
     Properties:
       QueueName: cf-test-queue-1
+      Tags:
+        - Key: key1
+          Value: value1
+        - Key: key2
+          Value: value2
   SNSTopic:
     Type: AWS::SNS::Topic
     Properties:
diff --git a/tests/integration/test_cloudformation.py b/tests/integration/test_cloudformation.py
--- a/tests/integration/test_cloudformation.py
+++ b/tests/integration/test_cloudformation.py
@@ -81,7 +81,7 @@ def queue_exists(name):
         return False
     for queue_url in queues['QueueUrls']:
         if queue_url == url:
-            return True
+            return queue_url
 
 
 def topic_exists(name):
@@ -146,6 +146,7 @@ def test_create_delete_stack(self):
         cf_client = aws_stack.connect_to_service('cloudformation')
         s3 = aws_stack.connect_to_service('s3')
         sns = aws_stack.connect_to_service('sns')
+        sqs = aws_stack.connect_to_service('sqs')
         apigateway = aws_stack.connect_to_service('apigateway')
         template = template_deployer.template_to_json(load_file(TEST_TEMPLATE_1))
 
@@ -162,7 +163,8 @@ def check_stack():
 
         # assert that resources have been created
         assert bucket_exists('cf-test-bucket-1')
-        assert queue_exists('cf-test-queue-1')
+        queue_url = queue_exists('cf-test-queue-1')
+        assert queue_url
         topic_arn = topic_exists('%s-test-topic-1-1' % stack_name)
         assert topic_arn
         assert stream_exists('cf-test-stream-1')
@@ -177,6 +179,9 @@ def check_stack():
             {'Key': 'foo', 'Value': 'cf-test-bucket-1'},
             {'Key': 'bar', 'Value': aws_stack.s3_bucket_arn('cf-test-bucket-1')}
         ])
+        queue_tags = sqs.list_queue_tags(QueueUrl=queue_url)
+        self.assertIn('Tags', queue_tags)
+        self.assertEqual(queue_tags['Tags'], {'key1': 'value1', 'key2': 'value2'})
 
         # assert that subscriptions have been created
         subs = sns.list_subscriptions()['Subscriptions']