def test_gh_63_value():
    """
    Test that Joins with conditionals that cannot evaluate to AWS::NoValue
    are converted to Fn::Sub
    """

    source = {
        "Fn::Join": [
            ",",
            [{
                "Fn::If": ["Condition1", "True1", "False1"]
            }, {
                "Fn::If": ["Condition2", "True2", "False2"]
            }]
        ]
    }

    expected = ODict((("Fn::Sub", [
        "${Param1},${Param2}",
        ODict((
            ("Param1", ODict((("Fn::If", ["Condition1", "True1",
                                          "False1"]), ))),
            ("Param2", ODict((("Fn::If", ["Condition2", "True2",
                                          "False2"]), ))),
        )),
    ]), ))

    actual = clean(source)

    assert actual == expected
def test_clean_flip_to_yaml_with_newlines():
    """
    Test that strings containing newlines use blockquotes when using "clean"
    """

    source = dump_json(ODict((
        ("outer", ODict((
            ("inner", "#!/bin/bash\nyum -y update\nyum install python"),
            ("subbed", ODict((
                ("Fn::Sub", "The cake\nis\n${CakeType}"),
            ))),
        ))),
    )))

    expected = """outer:
  inner: |-
    #!/bin/bash
    yum -y update
    yum install python
  subbed: !Sub |-
    The cake
    is
    ${CakeType}
"""

    assert cfn_flip.to_yaml(source, clean_up=True) == expected
def test_dump_yaml():
    """
    YAML dumping needs to use quoted style for strings with newlines,
    use a standard indenting style, and preserve order
    """

    source = ODict((
        (
            "z",
            "short string",
        ),
        (
            "m",
            {
                "Ref": "embedded string"
            },
        ),
        (
            "a",
            "A\nmulti-line\nstring",
        ),
    ))

    actual = dump_yaml(source)

    assert actual == """z: short string
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
def test_dump_json():
    """
    JSON dumping just needs to know about datetimes,
    provide a nice indent, and preserve order
    """
    import sys

    source = ODict((
        ("z", datetime.time(3, 45)),
        ("m", datetime.date(2012, 5, 2)),
        ("a", datetime.datetime(2012, 5, 2, 3, 45)),
    ))

    actual = dump_json(source)

    assert load_json(actual) == {
        "z": "03:45:00",
        "m": "2012-05-02",
        "a": "2012-05-02T03:45:00",
    }

    if sys.version_info < (3, 6):
        fail_message = r"\(1\+1j\) is not JSON serializable"
    elif sys.version_info < (3, 7):
        fail_message = "Object of type 'complex' is not JSON serializable"
    else:
        fail_message = "Object of type complex is not JSON serializable"

    with pytest.raises(TypeError, match=fail_message):
        dump_json({
            "c": 1 + 1j,
        })
def convert_join(sep, parts):
    """
    Fix a Join ;)
    """

    plain_string = True

    args = ODict()

    for i, part in enumerate(parts):
        part = clean(part)

        if isinstance(part, dict):
            plain_string = False

            if "Ref" in part:
                parts[i] = "${{{}}}".format(part["Ref"])
            elif "Fn::GetAtt" in part:
                params = part["Fn::GetAtt"]
                parts[i] = "${{{}}}".format(".".join(params))
            else:
                for name, value in args.items():
                    if value == part:
                        param_name = name
                        break
                else:
                    param_name = "Param{}".format(len(args) + 1)
                    args[param_name] = part

                parts[i] = "${{{}}}".format(param_name)

        else:
            parts[i] = part.replace("${", "${!")

    source = sep.join(parts)

    if plain_string:
        return source

    if args:
        return ODict((
            ("Fn::Sub", [source, args]),
        ))

    return ODict((
        ("Fn::Sub", source),
    ))
示例#7
0
def map_representer(dumper, value):
    """
    Deal with !Ref style function format and OrderedDict
    """

    value = ODict(value.items())

    if len(value.keys()) == 1:
        key = list(value.keys())[0]

        if key in CONVERTED_SUFFIXES:
            return fn_representer(dumper, key, value[key])

        if key.startswith(FN_PREFIX):
            return fn_representer(dumper, key[4:], value[key])

    return dumper.represent_mapping(TAG_MAP, value, flow_style=False)
def test_odict_fail_with_dict():
    """
    Raise exception if we pass dict when initializing the class with dict
    :return: Exception
    """
    items = {'key1': 'value1'}
    with pytest.raises(Exception) as e:
        ODict(items)
    assert 'ODict does not allow construction from a dict' == str(e.value)
示例#9
0
def test_post_deepcopy_repr():
    """
    Repr should behave normally after deepcopy
    """

    dct = ODict([("a", 1)])
    dct2 = deepcopy(dct)
    assert repr(dct) == repr(dct2)
    dct2["b"] = 2
    assert repr(dct) != repr(dct2)
示例#10
0
def test_ordering_from_constructor():
    """
    Ordering should be left intact
    """

    case = ODict([
        ("z", 1),
        ("a", 2),
    ])

    assert list(case.keys()) == ["z", "a"]
示例#11
0
def test_ordering():
    """
    Ordering should be left intact
    """

    case = ODict()

    case["z"] = 1
    case["a"] = 2

    assert list(case.keys()) == ["z", "a"]
示例#12
0
def test_constructor_disallows_dict():
    """
    For the sake of python<3.6, don't accept dicts
    as ordering will be lost
    """

    with pytest.raises(Exception,
                       match="ODict does not allow construction from a dict"):
        ODict({
            "z": 1,
            "a": 2,
        })
示例#13
0
def test_get_set():
    """
    It should at least work the same as a dict
    """

    case = ODict()

    case["one"] = 1
    case["two"] = 2

    assert len(case.keys()) == 2
    assert case["one"] == 1
示例#14
0
def test_pickle():
    """
    Should be able to pickle and unpickle
    """

    dct = ODict([
        ("c", 3),
        ("d", 4),
    ])
    data = pickle.dumps(dct)
    dct2 = pickle.loads(data)
    assert dct == dct2
def test_reused_sub_params():
    """
    Test that params in Joins converted to Subs get reused when possible
    """

    source = {
        "Fn::Join": [
            " ",
            [
                "The",
                {
                    "Fn::Join": ["-", [{
                        "Ref": "Cake"
                    }, "Lie"]],
                },
                "is",
                {
                    "Fn::Join": ["-", [{
                        "Ref": "Cake"
                    }, "Lie"]],
                },
                "and isn't",
                {
                    "Fn::Join": ["-", [{
                        "Ref": "Pizza"
                    }, "Truth"]],
                },
            ],
        ],
    }

    expected = ODict((("Fn::Sub", [
        "The ${Param1} is ${Param1} and isn't ${Param2}",
        ODict((
            ("Param1", ODict((("Fn::Sub", "${Cake}-Lie"), ))),
            ("Param2", ODict((("Fn::Sub", "${Pizza}-Truth"), ))),
        )),
    ]), ))

    assert clean(source) == expected
示例#16
0
def test_explicit_sorting():
    """
    Even an explicit sort should result in no change
    """

    case = ODict((
        ("z", 1),
        ("a", 2),
    )).items()

    actual = sorted(case)

    assert actual == case
示例#17
0
def test_list_constructor():
    """
    We should be able to construct one from a tuple of pairs
    """

    case = ODict((
        ("one", 1),
        ("two", 2),
    ))

    assert len(case.keys()) == 2
    assert case["one"] == 1
    assert case["two"] == 2
    assert case["two"] == 2
示例#18
0
def test_dump_json():
    """
    JSON dumping just needs to know about datetimes,
    provide a nice indent, and preserve order
    """

    source = ODict((
        ("z", datetime.time(3, 45)),
        ("m", datetime.date(2012, 5, 2)),
        ("a", datetime.datetime(2012, 5, 2, 3, 45)),
    ))

    actual = dump_json(source)

    assert load_json(actual) == {
        "z": "03:45:00",
        "m": "2012-05-02",
        "a": "2012-05-02T03:45:00",
    }

    with pytest.raises(TypeError, message="complex is not JSON serializable"):
        dump_json({
            "c": 1 + 1j,
        })
示例#19
0
def convert_join(value):
    """
    Fix a Join ;)
    """

    if not isinstance(value, list) or len(value) != 2:
        # Cowardly refuse
        return value

    sep, parts = value[0], value[1]

    if isinstance(parts, six.string_types):
        return parts

    if not isinstance(parts, list):
        # This looks tricky, just return the join as it was
        return {
            "Fn::Join": value,
        }

    plain_string = True

    args = ODict()
    new_parts = []

    for part in parts:
        part = clean(part)

        if isinstance(part, dict):
            plain_string = False

            if "Ref" in part:
                new_parts.append("${{{}}}".format(part["Ref"]))
            elif "Fn::GetAtt" in part:
                params = part["Fn::GetAtt"]
                new_parts.append("${{{}}}".format(".".join(params)))
            else:
                for key, val in args.items():
                    # we want to bail if a conditional can evaluate to AWS::NoValue
                    if isinstance(val, dict):
                        if "Fn::If" in val and "AWS::NoValue" in str(val["Fn::If"]):
                            return {
                                "Fn::Join": value,
                            }

                    if val == part:
                        param_name = key
                        break
                else:
                    param_name = "Param{}".format(len(args) + 1)
                    args[param_name] = part

                new_parts.append("${{{}}}".format(param_name))

        elif isinstance(part, six.string_types):
            new_parts.append(part.replace("${", "${!"))

        else:
            # Doing something weird; refuse
            return {
                "Fn::Join": value
            }

    source = sep.join(new_parts)

    if plain_string:
        return source

    if args:
        return ODict((
            ("Fn::Sub", [source, args]),
        ))

    return ODict((
        ("Fn::Sub", source),
    ))