def check_for_leftover_reference_keys(key, value): if key.startswith('|'): raise TemplateErrorException( "Unhandled reference key found: {0}".format(key)) if key.startswith('@') and key.endswith('@'): raise TemplateErrorException( "Unhandled reference key found: {0}".format(key)) return key, value
def check_for_leftover_reference_values(value): if isinstance(value, list): for item in value: if item.startswith('|'): raise TemplateErrorException( "Unhandled reference value found: {0}".format(value)) elif isinstance(value, string_types): if value.startswith('|'): raise TemplateErrorException( "Unhandled reference value found: {0}".format(value)) return value
def _s3_get_file(url): s3 = S3() try: if url.lower().endswith(".json"): return json.loads(s3.get_contents_from_url(url)) if url.lower().endswith(".yml") or url.lower().endswith(".yaml"): return yaml.load(s3.get_contents_from_url(url)) raise TemplateErrorException( "{0} has an unknown file type. Please provide an url with [.json|.yml|.yaml] extension" ) except Exception as e: raise TemplateErrorException( "Could not load file from {0}: {1}".format(url, e))
def check_for_leftover_reference_values(value): pattern = re.compile("^\|[a-zA-Z]+\|[a-zA-Z]+") if isinstance(value, string_types) and pattern.search(value): raise TemplateErrorException( "Unhandled reference value found: {0}".format(value)) return value
def transform_include_key(cls, key, value): if not value: return key, value if isinstance(key, string_types): if key.lower().strip() == '|include|': if not isinstance(value, string_types): raise TemplateErrorException( "Value of '|include|' must be of type string") if not value.lower().startswith("s3://"): raise TemplateErrorException( "Value of '|include|' must start with s3://") return "Fn::Transform", { "Name": "AWS::Include", "Location": value } return key, value
def get_cloudformation_template(cls, url, working_dir): """ Load file content from url and return cfn-sphere CloudFormationTemplate :param url: str :param working_dir: str :return: CloudFormationTemplate """ try: template_body_dict = cls.get_yaml_or_json_file(url, working_dir) return CloudFormationTemplate(body_dict=template_body_dict, name=os.path.basename(url)) except Exception as e: raise TemplateErrorException("Could not load file from {0}: {1}".format(url, e))
def _transform_dict(cls, dict_value, indentation_level=0, prefix=""): lines = [] for key, value in sorted(dict_value.items()): # key indentation with two spaces if indentation_level > 0: indented_key = " " * indentation_level + prefix + str(key) else: indented_key = prefix + str(key) if isinstance(key, string_types): # do not go any further and directly return cfn functions and their values if key.lower() == "ref" or key.lower().startswith("fn::"): indented_hyphen = ' ' * indentation_level + prefix # aws functions results will always be a string result = {key: value} line = cls.transform_kv_to_cfn_join(indented_hyphen, result, delimiter="") lines.append(line) else: # recursion for dict or list values if isinstance(value, dict): result = cls._transform_dict(value, indentation_level + 1) if isinstance(result, dict): lines.append( cls.transform_kv_to_cfn_join( indented_key, result)) elif isinstance(result, list): lines.append(indented_key + ":") lines.extend(result) else: raise TemplateErrorException( "Failed to convert dict to list of lines") elif isinstance(value, list): lines.append(indented_key + ":") lines.extend( cls._transform_list(value, indentation_level + 1)) elif isinstance(value, int): lines.append(indented_key + ": " + str(value)) elif isinstance(value, float): lines.append(indented_key + ": " + str(value)) else: lines.append(indented_key + ": '" + str(value) + "'") else: lines.append(cls.transform_kv_to_cfn_join(indented_key, value)) return lines
def transform_reference_string(value): if not value: return value if isinstance(value, string_types): if value.lower().startswith('|ref|'): referenced_value = value[5:] if not referenced_value: raise TemplateErrorException( "Reference must be like |ref|resource") return {'Ref': referenced_value} return value
def transform_join_key(cls, key, value): if not value: return key, value if isinstance(key, string_types): if key.lower().startswith('|join|'): if not isinstance(value, list): raise TemplateErrorException( "Value of '|join|' must be of type list") join_string = key[6:] return 'Fn::Join', [join_string, value] return key, value
def transform_yaml_user_data_key(cls, key, value): if not value: return key, value if isinstance(key, string_types): if str(key).lower() == '@yamluserdata@': if not isinstance(value, dict): raise TemplateErrorException( "Value of 'YamlUserData' must be of type dict") lines = cls.transform_dict_to_yaml_lines_list(value) return "UserData", {'Fn::Base64': {'Fn::Join': ['\n', lines]}} return key, value
def transform_dict_to_yaml_lines_list(cls, userdata_dict, indentation_level=0): lines = [] for key, value in userdata_dict.items(): # key indentation with two spaces if indentation_level > 0: indented_key = ' ' * indentation_level + str(key) else: indented_key = key if isinstance(key, string_types): # do not go any further and directly return cfn functions and their values if key.lower() in ['ref', 'fn::getatt', 'fn::join']: return {key: value} else: # recursion for dict values if isinstance(value, dict): result = cls.transform_dict_to_yaml_lines_list( value, indentation_level + 1) if isinstance(result, dict): lines.append( cls.transform_kv_to_cfn_join( indented_key, result)) elif isinstance(result, list): lines.append(indented_key + ':') lines.extend(result) else: raise TemplateErrorException( "Failed to convert dict to list of lines") elif isinstance(value, list): lines.extend([indented_key + ':'] + ['- {0}'.format(item) for item in value]) else: lines.append( cls.transform_kv_to_cfn_join(indented_key, value)) else: lines.append(cls.transform_kv_to_cfn_join(indented_key, value)) return lines
def transform_taupage_user_data_key(cls, key, value): if not value: return key, value if isinstance(key, string_types): if str(key).lower() == '@taupageuserdata@': if not isinstance(value, dict): raise TemplateErrorException( "Value of 'TaupageUserData' must be of type dict") lines = ['#taupage-ami-config'] lines.extend(cls.transform_dict_to_yaml_lines_list(value)) return "UserData", {'Fn::Base64': {'Fn::Join': ['\n', lines]}} return key, value
def transform_getattr_string(value): if not value: return value if isinstance(value, string_types): if value.lower().startswith('|getatt|'): elements = value.split('|', 3) if len(elements) != 4: raise TemplateErrorException( "Attribute reference must be like '|getatt|resource|attribute'" ) resource = elements[2] attribute = elements[3] return {'Fn::GetAtt': [resource, attribute]} return value
def _fs_get_file(url, working_dir): """ Load cfn template from filesystem :param url: str template path :return: dict repr of cfn template """ if not os.path.isabs(url) and working_dir: url = os.path.join(working_dir, url) try: with open(url, 'r') as template_file: if url.lower().endswith(".json"): return json.loads(template_file.read()) if url.lower().endswith(".yml") or url.lower().endswith( ".yaml"): return yaml.load(template_file.read()) except Exception as e: raise TemplateErrorException( "Could not load file from {0}: {1}".format(url, e))
def check_for_leftover_reference_keys(cls, key, value): if cls.is_reference_key(key): raise TemplateErrorException( "Unhandled reference key found: {0}".format(key)) return key, value