diff --git a/requirements.txt b/requirements.txt index 45f2ca9..9c53166 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,5 @@ -awscli>=1.11.130 configargparse>=0.9.3 -PyYAML<=3.13,>=3.10 +PyYAML>=4.2b1 Jinja2>=2.7.3 boto>=2.40.0 tabulate>=0.7.5 diff --git a/setup.py b/setup.py index 1c8f2ab..d40e18d 100644 --- a/setup.py +++ b/setup.py @@ -8,9 +8,8 @@ exec(f.read(), about) install_requires = [ - 'awscli>=1.11.130', 'configargparse>=0.9.3', - 'PyYAML<=3.13,>=3.10', + 'PyYAML>=4.2b1', 'Jinja2>=2.7.3', 'boto>=2.40.0', 'tabulate>=0.7.5', diff --git a/stacks/__about__.py b/stacks/__about__.py index 9282314..def7334 100644 --- a/stacks/__about__.py +++ b/stacks/__about__.py @@ -1,4 +1,4 @@ -__version__ = '0.4.3' +__version__ = '0.4.4' __licence__ = 'MIT' __url__ = 'https://stacks.tools' __maintainer__ = 'Vaidas Jablonskis' diff --git a/stacks/cf.py b/stacks/cf.py index d1e4b14..b6a133e 100644 --- a/stacks/cf.py +++ b/stacks/cf.py @@ -7,6 +7,7 @@ import json import sys import time +# noinspection PyProtectedMember from collections import Mapping, Set, Sequence from datetime import datetime from fnmatch import fnmatch @@ -18,12 +19,12 @@ import pytz import tzlocal import yaml -from awscli.customizations.cloudformation.yamlhelper import intrinsics_multi_constructor from boto.exception import BotoServerError from jinja2 import meta from tabulate import tabulate from stacks.aws import get_stack_tag, get_stack_template, throttling_retry +from stacks.helpers import intrinsics_multi_constructor from stacks.states import FAILED_STACK_STATES, COMPLETE_STACK_STATES, ROLLBACK_STACK_STATES, IN_PROGRESS_STACK_STATES YES = ['y', 'Y', 'yes', 'YES', 'Yes'] diff --git a/stacks/helpers.py b/stacks/helpers.py new file mode 100644 index 0000000..8dce0f4 --- /dev/null +++ b/stacks/helpers.py @@ -0,0 +1,40 @@ +# noinspection PyProtectedMember +from yaml.resolver import ScalarNode, SequenceNode + + +# noinspection PyUnusedLocal +def intrinsics_multi_constructor(loader, tag_prefix, node): + """ + YAML constructor to parse CloudFormation intrinsics. + This will return a dictionary with key being the instrinsic name + """ + + # Get the actual tag name excluding the first exclamation + tag = node.tag[1:] + + # Some intrinsic functions doesn't support prefix "Fn::" + prefix = "Fn::" + if tag in ["Ref", "Condition"]: + prefix = "" + + cfntag = prefix + tag + + if tag == "GetAtt" and isinstance(node.value, str): + # ShortHand notation for !GetAtt accepts Resource.Attribute format + # while the standard notation is to use an array + # [Resource, Attribute]. Convert shorthand to standard format + value = node.value.split(".", 1) + + elif isinstance(node, ScalarNode): + # Value of this node is scalar + value = loader.construct_scalar(node) + + elif isinstance(node, SequenceNode): + # Value of this node is an array (Ex: [1,2]) + value = loader.construct_sequence(node) + + else: + # Value of this node is an mapping (ex: {foo: bar}) + value = loader.construct_mapping(node) + + return {cfntag: value}