class CfnParser
This class is the heart of the matter. It will take a CloudFormation template and return a CfnModel
object to represent the underlying document in a way that is hopefully more convenient for (cfn-nag rule) developers to work with
Public Instance Methods
parse(cloudformation_yml, parameter_values_json=nil, with_line_numbers=false, condition_values_json=nil)
click to toggle source
Given raw json/yml CloudFormation template, returns a CfnModel
object or raise ParserErrors if something is amiss with the format
# File lib/cfn-model/parser/cfn_parser.rb, line 42 def parse(cloudformation_yml, parameter_values_json=nil, with_line_numbers=false, condition_values_json=nil) cfn_model = parse_without_parameters(cloudformation_yml, with_line_numbers, condition_values_json) apply_parameter_values(cfn_model, parameter_values_json) # pass 2: tie together separate resources only where necessary to make life easier for rule logic post_process_resource_model_elements cfn_model cfn_model end
parse_with_line_numbers(cloudformation_yml)
click to toggle source
# File lib/cfn-model/parser/cfn_parser.rb, line 53 def parse_with_line_numbers(cloudformation_yml) handler = LineNumberHandler.new parser = Psych::Parser.new(handler) handler.parser = parser parser.parse(cloudformation_yml) ToRubyWithLineNumbers.create.accept(handler.root).first end
parse_without_parameters(cloudformation_yml, with_line_numbers=false, condition_values_json=nil)
click to toggle source
# File lib/cfn-model/parser/cfn_parser.rb, line 61 def parse_without_parameters(cloudformation_yml, with_line_numbers=false, condition_values_json=nil) pre_validate_model cloudformation_yml cfn_hash = if with_line_numbers parse_with_line_numbers(cloudformation_yml) else YAML.load cloudformation_yml end # Transform raw resources in template as performed by # transforms CfnModel::TransformRegistry.instance.perform_transforms cfn_hash validate_references cfn_hash cfn_model = CfnModel.new cfn_model.raw_model = cfn_hash process_conditions cfn_hash, cfn_model, condition_values_json process_mappings cfn_hash, cfn_model # pass 1: wire properties into ModelElement objects if with_line_numbers transform_hash_into_model_elements_with_numbers cfn_hash, cfn_model else transform_hash_into_model_elements cfn_hash, cfn_model end transform_hash_into_parameters cfn_hash, cfn_model transform_hash_into_globals cfn_hash, cfn_model cfn_model end
Private Instance Methods
apply_parameter_values(cfn_model, parameter_values_json)
click to toggle source
# File lib/cfn-model/parser/cfn_parser.rb, line 126 def apply_parameter_values(cfn_model, parameter_values_json) ParameterSubstitution.new.apply_parameter_values( cfn_model, parameter_values_json ) end
assign_fields_based_upon_properties(resource_object, resource, cfn_model)
click to toggle source
# File lib/cfn-model/parser/cfn_parser.rb, line 246 def assign_fields_based_upon_properties(resource_object, resource, cfn_model) unless resource['Properties'].nil? deal_with_conditional_property_definitions(resource, cfn_model) resource['Properties'].each do |property_name, property_value| next if %w(Fn::Transform).include? property_name resource_object.send("#{map_property_name_to_attribute(property_name)}=", map_property_value(property_value, cfn_model)) end end end
class_from_type_name(type_name)
click to toggle source
# File lib/cfn-model/parser/cfn_parser.rb, line 257 def class_from_type_name(type_name) begin resource_class = Object.const_get type_name, inherit=false rescue NameError # puts "Never seen class: #{type_name} so going dynamic" resource_class = generate_resource_class_from_type type_name end resource_class end
clean_module_name(module_name)
click to toggle source
strip any characters that are legal in a resource name that are going to make for a legal character in a ruby class name
# File lib/cfn-model/parser/cfn_parser.rb, line 278 def clean_module_name(module_name) module_name.gsub /[\-@]/, '' end
deal_with_conditional_property_definitions(resource, cfn_model)
click to toggle source
# File lib/cfn-model/parser/cfn_parser.rb, line 228 def deal_with_conditional_property_definitions(resource, cfn_model) all_extra_concrete_properties = [] resource['Properties'].each do |property_name, property_value| next if %w(Fn::Transform).include? property_name if property_name == 'Fn::If' concrete_properties = ExpressionEvaluator.new.evaluate( {'Fn::If'=>property_value}, cfn_model.conditions ) all_extra_concrete_properties << concrete_properties end end all_extra_concrete_properties.each do |extra_concrete_properties| resource['Properties'].merge!(extra_concrete_properties) end resource['Properties'].delete('Fn::If') end
generate_resource_class_from_type(type_name)
click to toggle source
# File lib/cfn-model/parser/cfn_parser.rb, line 296 def generate_resource_class_from_type(type_name) resource_class = Class.new(ModelElement) module_names = type_name.split('::') if module_names.first == 'AWS' begin module_constant = AWS.const_get(module_names[1]) rescue NameError module_constant = Module.new module_constant.const_set(module_names[1], module_constant) end module_constant.const_set(module_names[2], resource_class) else custom_resource_class_name = map_non_aws_resource_name_to_class_name(module_names) begin custom_class = Object.const_get custom_resource_class_name resource_class = custom_class if custom_class.is_a?(ModelElement) rescue NameError Object.const_set(custom_resource_class_name, resource_class) end end resource_class end
initial_upper(str)
click to toggle source
# File lib/cfn-model/parser/cfn_parser.rb, line 320 def initial_upper(str) str.slice(0).upcase + str[1..(str.length)] end
map_non_aws_resource_name_to_class_name(module_names)
click to toggle source
# File lib/cfn-model/parser/cfn_parser.rb, line 282 def map_non_aws_resource_name_to_class_name(module_names) # this is a little hacky. we've been ignoring Custom so more for # backward compat. for Alexa and other transformed resources just jam the whole # thing together if module_names.first == 'Custom' first_module_index = 1 else first_module_index = 0 end module_names[first_module_index..-1].reduce('') do |class_name, module_name| class_name + initial_upper(clean_module_name(module_name)) end end
map_property_name_to_attribute(str)
click to toggle source
# File lib/cfn-model/parser/cfn_parser.rb, line 271 def map_property_name_to_attribute(str) (str.slice(0).downcase + str[1..(str.length)]).gsub /-/, '_' end
map_property_value(property_value, cfn_model)
click to toggle source
# File lib/cfn-model/parser/cfn_parser.rb, line 267 def map_property_value(property_value, cfn_model) ExpressionEvaluator.new.evaluate(property_value, cfn_model.conditions) end
post_process_resource_model_elements(cfn_model)
click to toggle source
# File lib/cfn-model/parser/cfn_parser.rb, line 133 def post_process_resource_model_elements(cfn_model) cfn_model.resources.each do |_, resource| resource_parser_class = ParserRegistry.instance.registry[resource.class.to_s] next if resource_parser_class.nil? resource_parser = resource_parser_class.new resource_parser.parse(cfn_model: cfn_model, resource: resource) end end
pre_validate_model(cloudformation_yml)
click to toggle source
# File lib/cfn-model/parser/cfn_parser.rb, line 214 def pre_validate_model(cloudformation_yml) errors = CloudFormationValidator.new.validate cloudformation_yml if !errors.nil? && !errors.empty? raise ParserError.new('Basic CloudFormation syntax error', errors) end end
process_conditions(cfn_hash, cfn_model, condition_values_json)
click to toggle source
# File lib/cfn-model/parser/cfn_parser.rb, line 108 def process_conditions(cfn_hash, cfn_model, condition_values_json) if cfn_hash.key?('Conditions') if condition_values_json.nil? condition_values = {} else condition_values = JSON.load condition_values_json end cfn_hash['Conditions'].each do |condition_key, _| if condition_values.key?(condition_key) && [true, false].include?(condition_values[condition_key]) cfn_model.conditions[condition_key] = condition_values[condition_key] else cfn_model.conditions[condition_key] = true end end end end
process_mappings(cfn_hash, cfn_model)
click to toggle source
# File lib/cfn-model/parser/cfn_parser.rb, line 100 def process_mappings(cfn_hash, cfn_model) if cfn_hash.key?('Mappings') cfn_hash['Mappings'].each do |mapping_key, mapping_value| cfn_model.mappings[mapping_key] = mapping_value end end end
transform_hash_into_globals(cfn_hash, cfn_model)
click to toggle source
# File lib/cfn-model/parser/cfn_parser.rb, line 198 def transform_hash_into_globals(cfn_hash, cfn_model) return cfn_model unless cfn_hash.key?('Globals') cfn_hash['Globals'].each do |resource, parameter_hash| global = Parameter.new global.id = resource parameter_hash.each do |property_name, property_value| global.send("#{map_property_name_to_attribute(property_name)}=", property_value) end cfn_model.globals[resource] = global end cfn_model end
transform_hash_into_model_elements(cfn_hash, cfn_model)
click to toggle source
pass 0: validate basic syntax so we can make some assumptions down stream
even within the parsing code
# File lib/cfn-model/parser/cfn_parser.rb, line 147 def transform_hash_into_model_elements(cfn_hash, cfn_model) cfn_hash['Resources'].each do |resource_name, resource| resource_class = class_from_type_name resource['Type'] resource_object = resource_class.new(cfn_model) resource_object.logical_resource_id = resource_name resource_object.resource_type = resource['Type'] resource_object.metadata = resource['Metadata'] assign_fields_based_upon_properties resource_object, resource, cfn_model cfn_model.resources[resource_name] = resource_object end cfn_model end
transform_hash_into_model_elements_with_numbers(cfn_hash, cfn_model)
click to toggle source
# File lib/cfn-model/parser/cfn_parser.rb, line 163 def transform_hash_into_model_elements_with_numbers(cfn_hash, cfn_model) cfn_hash['Resources'].each do |resource_name, resource| resource_class = class_from_type_name resource['Type']['value'] resource_object = resource_class.new(cfn_model) resource_object.logical_resource_id = resource_name resource_object.resource_type = resource['Type']['value'] resource_object.metadata = resource['Metadata'] assign_fields_based_upon_properties resource_object, resource, cfn_model cfn_model.resources[resource_name] = resource_object cfn_model.line_numbers[resource_name] = resource['Type']['line'] end cfn_model end
transform_hash_into_parameters(cfn_hash, cfn_model)
click to toggle source
# File lib/cfn-model/parser/cfn_parser.rb, line 180 def transform_hash_into_parameters(cfn_hash, cfn_model) return cfn_model unless cfn_hash.key?('Parameters') cfn_hash['Parameters'].each do |parameter_name, parameter_hash| parameter = Parameter.new parameter.id = parameter_name parameter.type = parameter_hash['Type'] parameter_hash.each do |property_name, property_value| next if %w(Type).include? property_name parameter.send("#{map_property_name_to_attribute(property_name)}=", property_value) end cfn_model.parameters[parameter_name] = parameter end cfn_model end
validate_references(cfn_hash)
click to toggle source
# File lib/cfn-model/parser/cfn_parser.rb, line 221 def validate_references(cfn_hash) unresolved_refs = ReferenceValidator.new.unresolved_references(cfn_hash) unless unresolved_refs.empty? raise ParserError.new("Unresolved logical resource ids: #{unresolved_refs.to_a}") end end