Skip to content

Stack url helper

taskcat._cfn.stack_url_helper

Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

StackURLHelper(template_mappings=None, template_parameters=None, parameter_values=None)

Source code in taskcat/_cfn/stack_url_helper.py
def __init__(
    self,
    template_mappings=None,
    template_parameters=None,
    parameter_values=None,
):
    if template_mappings:
        self.mappings = template_mappings
    else:
        self.mappings = {}

    if template_parameters:
        self.template_parameters = template_parameters
    else:
        self.template_parameters = {}

    if parameter_values:
        self.parameter_values = parameter_values
    else:
        self.parameter_values = {}

    default_parameters: dict = {}
    for parameter in self.template_parameters:
        properties = self.template_parameters.get(parameter)
        if "Default" in properties.keys():
            default_parameters[parameter] = properties["Default"]

    self.SUBSTITUTION.update(default_parameters)
    self.SUBSTITUTION.update(self.parameter_values)

evaluate_expression_controller(expression)

Figure out what type of expression and pass off to handler

Source code in taskcat/_cfn/stack_url_helper.py
def evaluate_expression_controller(self, expression):
    """Figure out what type of expression and pass off to handler"""
    results = []

    if "Fn::If" in expression:
        results = self.evaluate_fn_if(expression)

    elif "Fn::Sub" in expression:
        results = self.evaluate_fn_sub(expression)

    elif "Fn::Join" in expression:
        results = self.evaluate_fn_join(expression)

    elif "Ref" in expression:
        results = self.evaluate_fn_ref(expression)

    elif "Fn::FindInMap" in expression:
        results = self.evaluate_fn_findinmap(expression)

    elif "Fn::GetAtt" in expression:
        results = self.evaluate_fn_getatt(expression)

    elif "Fn::Split" in expression:
        results = self.evaluate_fn_split(expression)

    else:
        # This is a NON expression repl { and } with ( and ) to break recursion
        results.append("(" + expression + ")")

    return results

evaluate_fn_if(expression) staticmethod

Return both possible parts of the expression

Source code in taskcat/_cfn/stack_url_helper.py
@staticmethod
def evaluate_fn_if(expression):
    """Return both possible parts of the expression"""
    results = []
    value_true = expression.split(",")[1].strip()
    value_false = expression.split(",")[2].strip().strip("]")
    # if we don't have '' this can break things
    results.append("'" + value_true.strip("'") + "'")
    results.append("'" + value_false.strip("'") + "'")
    return results

evaluate_fn_join(expression) staticmethod

Return the joined stuff

Source code in taskcat/_cfn/stack_url_helper.py
@staticmethod
def evaluate_fn_join(expression):
    """Return the joined stuff"""
    results = []
    new_values_list = []

    temp = expression.split("[")[1]
    delimiter = temp.split(",")[0].strip("'")

    values = expression.split("[")[2]
    values = values.split("]]")[0]

    values_list = values.split(", ")
    for value in values_list:
        new_values_list.append(value.strip("'"))

    result = delimiter.join(new_values_list)
    results.append(result)

    return results

evaluate_fn_ref(expression)

Since this is runtime data the best we can do is the name in place

Source code in taskcat/_cfn/stack_url_helper.py
def evaluate_fn_ref(self, expression):
    """Since this is runtime data the best we can do is the name in place"""
    # TODO: Allow user to inject RunTime values for these
    results = []

    temp = expression.split(": ")[1]
    # pylint: disable=consider-iterating-dictionary
    if temp.strip("'") in self.SUBSTITUTION.keys():
        temp = self.SUBSTITUTION[temp.strip("'")]
        temp = "'" + temp + "'"

    results.append(temp)

    return results

evaluate_fn_sub(expression)

Return expression with values replaced

Source code in taskcat/_cfn/stack_url_helper.py
def evaluate_fn_sub(self, expression):
    """Return expression with values replaced"""
    results = []

    # Builtins - Fudge some defaults here since we don't have runtime info
    # ${AWS::Region} ${AWS::AccountId}
    expression = self.rewrite_sub_vars_with_values(expression, self.SUBSTITUTION)

    # Handle Sub of form [ StringToSub, { "key" : "value", "key": "value" }]
    if "[" in expression:
        temp_expression = expression.split("[")[1].split(",")[0]
        values = expression.split("[")[1].split("(")[1].split(")")[0]
        values = self.values_to_dict("(" + values + ")")
        temp_expression = self.rewrite_sub_vars_with_values(temp_expression, values)
    else:
        temp_expression = expression.split("': '")[1].split("'")[0]

    # if we still have them we just use their values (ie: Parameters)
    result = self.rewrite_sub_vars(temp_expression)

    results.append(result)

    return results

evaluate_string(template_url, depth=0)

Recursively find expressions in the URL and send them to be evaluated

Source code in taskcat/_cfn/stack_url_helper.py
def evaluate_string(self, template_url, depth=0):
    """Recursively find expressions in the URL and send them to be evaluated"""
    # Recursion bail out
    if depth > self.MAX_DEPTH:
        raise Exception(
            "Template URL contains more than {} levels or nesting".format(
                self.MAX_DEPTH
            )
        )

    template_urls = []
    # Evaluate expressions
    if "{" in template_url:
        parts = template_url.split("{")
        parts = parts[-1].split("}")  # Last open bracket

        # This function will handle Fn::Sub Fn::If etc.
        replacements = self.evaluate_expression_controller(
            parts[0]
        )  # First closed bracket after

        for replacement in replacements:
            template_url_temp = template_url
            template_url_temp = template_url_temp.replace(
                "{" + parts[0] + "}", replacement
            )

            evaluated_strings = self.evaluate_string(
                template_url_temp, depth=(depth + 1)
            )
            for evaluated_string in evaluated_strings:
                template_urls.append(evaluated_string)
    else:
        template_urls.append(template_url)

    return template_urls

flatten_template_url(template_url)

Flatten template_url and return all permutations

Source code in taskcat/_cfn/stack_url_helper.py
def flatten_template_url(self, template_url):
    """Flatten template_url and return all permutations"""
    path_list = []

    url_list = self._flatten_template_controller(template_url)

    # Extract the path portion from the URL
    for url in url_list:
        # TODO: figure where the ' is coming from
        output = urlparse(str(url.strip("'")))
        path_list.append(output.path)

    path_list = list(dict.fromkeys(path_list))
    # print(url_list)
    # print(path_list)
    return path_list

rewrite_sub_vars(original_string, depth=1)

Replace the '##var##' placeholders with 'var'

Source code in taskcat/_cfn/stack_url_helper.py
def rewrite_sub_vars(self, original_string, depth=1):
    """Replace the '##var##' placeholders with 'var'"""
    if "##" not in original_string:
        return original_string

    parts = original_string.split("##")
    parts = parts[1].split("##")

    rep_text = "##" + parts[0] + "##"
    rep_with = "" + parts[0] + ""

    result = original_string.replace(rep_text, rep_with)

    if "##" in result:  # Recurse if we have more variables
        result = self.rewrite_sub_vars(result, depth=depth + 1)

    return result

rewrite_sub_vars_with_values(expression, values) staticmethod

Rewrite sub vars with actual variable values

Source code in taskcat/_cfn/stack_url_helper.py
@staticmethod
def rewrite_sub_vars_with_values(expression, values):
    """Rewrite sub vars with actual variable values"""
    result = expression

    # replace each key we have a value for
    for key in values:
        rep_text = "##" + key + "##"
        rep_with = "" + str(values[key]) + ""

        result = result.replace(rep_text, rep_with)

    return result

rewrite_vars(original_string, depth=1)

Replace the ${var} placeholders with ##var##

Source code in taskcat/_cfn/stack_url_helper.py
def rewrite_vars(self, original_string, depth=1):
    """Replace the ${var} placeholders with ##var##"""
    parts = original_string.split("${")
    parts = parts[1].split("}")

    rep_text = "${" + parts[0] + "}"
    rep_with = "##" + parts[0] + "##"

    result = original_string.replace(rep_text, rep_with)

    if len(result.split("${")) > 1:
        result = self.rewrite_vars(result, depth=depth + 1)

    return result

values_to_dict(values) staticmethod

Rewrite sub vars with actual variable values

Source code in taskcat/_cfn/stack_url_helper.py
@staticmethod
def values_to_dict(values):
    """Rewrite sub vars with actual variable values"""
    # Create dictionary of values
    values_dict_string = values.replace("(", "{")
    values_dict_string = values_dict_string.replace(")", "}")
    values_dict_string = values_dict_string.replace("'", '"')

    # for values or keys not quoted
    # Split by :
    values_split_string = values_dict_string
    # Trim stuff so we can get the key values
    values_split_string = values_split_string.replace(" ", "")
    values_split_string = values_split_string.replace("{", "")
    values_split_string = values_split_string.replace("}", "")

    values_split = values_split_string.split(",")
    values_split_final = []
    for value in values_split:
        values = value.split(":")
        values_split_final.extend(values)

    for value in values_split_final:
        if value[0] != "'" and value[-1] != "'":
            if value[0] != '"' and value[-1] != '"':
                values_dict_string = values_dict_string.replace(
                    value, '"' + value + '"'
                )

    values_dict = json.loads(values_dict_string)

    return values_dict