Repository: aws-ia/taskcat
Issue: Exception while processing child stack

**Describe the bug**

I got an error while run command ` taskcat -d test run`

```
[ERROR  ] : TypeError write() argument must be str, not collections.OrderedDict
Traceback (most recent call last):
  File "/home/ags/venv/venv-taskcat/lib/python3.6/site-packages/taskcat/_cli.py", line 66, in main
    cli.run()
  File "/home/ags/venv/venv-taskcat/lib/python3.6/site-packages/taskcat/_cli_core.py", line 228, in run
    return getattr(command(), subcommand)(**args)
  File "/home/ags/venv/venv-taskcat/lib/python3.6/site-packages/taskcat/_cli_modules/test.py", line 81, in run
    terminal_printer.report_test_progress(stacker=test_definition)
  File "/home/ags/venv/venv-taskcat/lib/python3.6/site-packages/taskcat/_tui.py", line 24, in report_test_progress
    self._print_stack_tree(stack, buffer=self.buffer)
  File "/home/ags/venv/venv-taskcat/lib/python3.6/site-packages/taskcat/_tui.py", line 37, in _print_stack_tree
    if stack.descendants():
  File "/home/ags/venv/venv-taskcat/lib/python3.6/site-packages/taskcat/_cfn/stack.py", line 495, in descendants
    self._fetch_children()
  File "/home/ags/venv/venv-taskcat/lib/python3.6/site-packages/taskcat/_cfn/stack.py", line 481, in _fetch_children
    stack_obj = Stack._import_child(stack, self)
  File "/home/ags/venv/venv-taskcat/lib/python3.6/site-packages/taskcat/_cfn/stack.py", line 335, in _import_child
    fh.write(tempate_body)
TypeError: write() argument must be str, not collections.OrderedDict
```

**Version (Please make sure you are running the latest version of taskcat)
 
```
Name: taskcat
Version: 0.9.0
Summary: An OpenSource Cloudformation Deployment Framework
Home-page: https://aws-quickstart.github.io/taskcat/
Author: Tony Vattathil, Jay McConnell, Andrew Glenn, Santiago Cardenas, Shivansh Singh
Author-email: tonynv@amazon.com, jmmccon@amazon.com, andglenn@amazon.com, sshvans@amazon.com
License: Apache License 2.0
Location: /home/ags/venv/venv-taskcat/lib/python3.6/site-packages
Requires: boto3, pyfiglet, yattag, PyYAML, tabulate, jinja2, jsonschema, mock, dataclasses-jsonschema, requests, reprint, dataclasses, setuptools, cfn-lint, docker, dulwich, botocore
Required-by: 
```

**Additional context**

I use custom cloudformation template with child stacks.

It looks like that error here (_cfn/stack.py) - write expect str not an OrderedDict:

```python
...
            if not absolute_path.exists():
                with open(absolute_path, "w") as fh:
                    fh.write(tempate_body)
...
```


Changed files:
taskcat/_common_utils.py, taskcat/_cfn/stack.py

diff --git a/taskcat/_cfn/stack.py b/taskcat/_cfn/stack.py
index 0cd15a3..4427eb9 100644
--- a/taskcat/_cfn/stack.py
+++ b/taskcat/_cfn/stack.py
@@ -11,9 +11,10 @@ from typing import Callable, List, Optional, Tuple
 from uuid import UUID, uuid4
 
 import boto3
+import yaml
 
 from taskcat._cfn.template import Template
-from taskcat._common_utils import pascal_to_snake, s3_url_maker
+from taskcat._common_utils import ordered_dump, pascal_to_snake, s3_url_maker
 from taskcat._dataclasses import TestRegion
 
 LOG = logging.getLogger(__name__)
@@ -295,7 +296,7 @@ class Stack:  # pylint: disable=too-many-instance-attributes
         return [{"ParameterKey": k, "ParameterValue": v} for k, v in parameters.items()]
 
     @classmethod
-    def _import_child(
+    def _import_child(  # pylint: disable=too-many-locals
         cls, stack_properties: dict, parent_stack: "Stack"
     ) -> Optional["Stack"]:
         url = ""
@@ -309,23 +310,33 @@ class Stack:  # pylint: disable=too-many-instance-attributes
             )
             absolute_path = parent_stack.template.project_root / relative_path
         else:
-            # Assuming template is remote to project and downloading it
-            cfn_client = parent_stack.client
-            tempate_body = cfn_client.get_template(
-                StackName=stack_properties["StackId"]
-            )["TemplateBody"]
-            path = parent_stack.template.project_root / Stack.REMOTE_TEMPLATE_PATH
-            os.makedirs(path, exist_ok=True)
-            fname = (
-                "".join(
-                    random.choice(string.ascii_lowercase) for _ in range(16)  # nosec
+            try:
+                # Assuming template is remote to project and downloading it
+                cfn_client = parent_stack.client
+                tempate_body = cfn_client.get_template(
+                    StackName=stack_properties["StackId"]
+                )["TemplateBody"]
+                path = parent_stack.template.project_root / Stack.REMOTE_TEMPLATE_PATH
+                os.makedirs(path, exist_ok=True)
+                fname = (
+                    "".join(
+                        random.choice(string.ascii_lowercase)  # nosec
+                        for _ in range(16)
+                    )
+                    + ".template"
                 )
-                + ".template"
-            )
-            absolute_path = path / fname
-            if not absolute_path.exists():
-                with open(absolute_path, "w") as fh:
-                    fh.write(tempate_body)
+                absolute_path = path / fname
+                template_str = ordered_dump(tempate_body, Dumper=yaml.SafeDumper)
+                if not absolute_path.exists():
+                    with open(absolute_path, "w") as fh:
+                        fh.write(template_str)
+            except Exception as e:  # pylint: disable=broad-except
+                LOG.warning(
+                    f"Failed to attach child stack "
+                    f'{stack_properties["StackId"]} {str(e)}'
+                )
+                LOG.debug("traceback", exc_info=True)
+                return None
         template = Template(
             template_path=str(absolute_path),
             project_root=parent_stack.template.project_root,
@@ -472,7 +483,8 @@ class Stack:  # pylint: disable=too-many-instance-attributes
                 if "ParentId" in stack.keys():
                     if self.id == stack["ParentId"]:
                         stack_obj = Stack._import_child(stack, self)
-                        self._children.append(stack_obj)
+                        if stack_obj:
+                            self._children.append(stack_obj)
 
     def children(self, refresh=False) -> Stacks:
         if (
diff --git a/taskcat/_common_utils.py b/taskcat/_common_utils.py
index cde5a10..44ceb8c 100644
--- a/taskcat/_common_utils.py
+++ b/taskcat/_common_utils.py
@@ -5,8 +5,10 @@ import random
 import re
 import string
 import sys
+from collections import OrderedDict
 
 import boto3
+import yaml
 
 from taskcat.exceptions import TaskCatException
 
@@ -143,3 +145,16 @@ def merge_nested_dict(old, new):
             merge_nested_dict(old[k], v)
         else:
             old[k] = v
+
+
+def ordered_dump(data, stream=None, dumper=yaml.Dumper, **kwds):
+    class OrderedDumper(dumper):  # pylint: disable=too-many-ancestors
+        pass
+
+    def _dict_representer(dumper, data):
+        return dumper.represent_mapping(
+            yaml.resolver.BaseResolver.DEFAULT_MAPPING_TAG, data.items()
+        )
+
+    OrderedDumper.add_representer(OrderedDict, _dict_representer)
+    return yaml.dump(data, stream, OrderedDumper, **kwds)