Repository: awslabs/aws-cfn-template-flip

Issue:
Inconsistent conversion of strings from json to yaml
I am converting a document from json to yaml as part of a CloudFormation Template, and am noticing an odd error where some Id's that are marked as strings are being converted to strings, and other times not.

Here's a json snippet I'm working with right now which are the mappings for some of the Generic Elastic Load Balancer ID's for AWS:

```    "Mappings": {
        "Regions": {
            "us-east-1": {
                "ELBID": "127311923021",
                "Name": "ue1"
            },
            "us-east-2": {
                "ELBID": "033677994240",
                "Name": "ue2"
            },
            "us-west-1": {
                "ELBID": "027434742980",
                "Name": "uw1"
            },
            "us-west-2": {
                "ELBID": "797873946194",
                "Name": "uw2"
            }
        }
    }
```

And This is the resulting yaml I'm getting after calling to_yaml:

```
Mappings:
  Regions:
    us-east-1:
      ELBID: '127311923021'
      Name: ue1
    us-east-2:
      ELBID: 033677994240
      Name: ue2
    us-west-1:
      ELBID: 027434742980
      Name: uw1
    us-west-2:
      ELBID: '797873946194'
      Name: uw2
```

Strangely enough, any number beginning with 0 is converted, but the ones beginning with other numbers do not.  I'm not sure what the expected behavior should be in this case, (either fully converted or not) but having it half and half is inconsistent, and I would believe is a bug.

Currently I'm having errors with using this yaml with sceptre/CloudFormation due to some of the Elastic Load Balancer ID's not being strings.

Comments:

Patch:
diff --git a/cfn_flip/yaml_dumper.py b/cfn_flip/yaml_dumper.py
index 85b287d..2a3a764 100644
--- a/cfn_flip/yaml_dumper.py
+++ b/cfn_flip/yaml_dumper.py
@@ -15,7 +15,9 @@ See the License for the specific language governing permissions and limitations
 from cfn_clean.yaml_dumper import CleanCfnYamlDumper
 from cfn_tools.odict import ODict
 from cfn_tools.yaml_dumper import CfnYamlDumper
+import six
 
+TAG_STR = "tag:yaml.org,2002:str"
 TAG_MAP = "tag:yaml.org,2002:map"
 CONVERTED_SUFFIXES = ["Ref", "Condition"]
 
@@ -46,6 +48,13 @@ class LongCleanDumper(CleanCfnYamlDumper):
     """
 
 
+def string_representer(dumper, value):
+    if value.startswith("0"):
+        return dumper.represent_scalar(TAG_STR, value, style="'")
+
+    return dumper.represent_scalar(TAG_STR, value)
+
+
 def fn_representer(dumper, fn_name, value):
     tag = "!{}".format(fn_name)
 
@@ -82,6 +91,7 @@ def map_representer(dumper, value):
 
 # Customise our dumpers
 Dumper.add_representer(ODict, map_representer)
+Dumper.add_representer(six.text_type, string_representer)
 CleanDumper.add_representer(ODict, map_representer)
 
 


Test patch:
diff --git a/tests/test_flip.py b/tests/test_flip.py
index c479a20..5ac0cee 100644
--- a/tests/test_flip.py
+++ b/tests/test_flip.py
@@ -502,5 +502,39 @@ def test_get_dumper():
     When invoking get_dumper use clean_up & long_form
     :return: LongCleanDumper
     """
+
     resp = cfn_flip.get_dumper(clean_up=True, long_form=True)
     assert resp == cfn_flip.yaml_dumper.LongCleanDumper
+
+
+def test_quoted_digits():
+    """
+    Any value that is composed entirely of digits
+    should be quoted for safety.
+    CloudFormation is happy for numbers to appear as strings.
+    But the opposite (e.g. account numbers as numbers) can cause issues
+    See https://github.com/awslabs/aws-cfn-template-flip/issues/41
+    """
+
+    value = dump_json(ODict((
+        ("int", 123456),
+        ("float", 123.456),
+        ("oct", "0123456"),
+        ("bad-oct", "012345678"),
+        ("safe-oct", "0o123456"),
+        ("string", "abcdef"),
+    )))
+
+    expected = "\n".join((
+        "int: 123456",
+        "float: 123.456",
+        "oct: '0123456'",
+        "bad-oct: '012345678'",
+        "safe-oct: '0o123456'",
+        "string: abcdef",
+        ""
+    ))
+
+    actual = cfn_flip.to_yaml(value)
+
+    assert actual == expected