lib/lono/template/template.rb in lono-2.1.0 vs lib/lono/template/template.rb in lono-3.0.0

- old
+ new

@@ -1,65 +1,104 @@ require 'erb' require 'json' require 'base64' class Lono::Template::Template + include Lono::Template::Helpers include ERB::Util - attr_reader :name - def initialize(name, block, options={}) - @name = name - @block = block - @options = options + def initialize(name, block=nil, options={}) + # Taking care to name instance variables with _ in front because we load the + # variables from config/variables and those instance variables can clobber these + # instance variables + @_name = name + @_options = options + @_detected_format = options[:detected_format] + @_block = block + @_project_root = options[:project_root] || '.' + @_config_path = "#{@_project_root}/config" + @_source = default_source(name) end + def default_source(name) + "#{@_project_root}/templates/#{name}.#{@_detected_format}" # defaults to name, source method overrides + end + def build - instance_eval(&@block) - template = IO.read(@source) - erb_result(@source, template) + load_variables + load_custom_helpers + instance_eval(&@_block) if @_block + template = IO.read(@_source) + erb_result(@_source, template) end - def source(path) - @source = path[0..0] == '/' ? path : "#{@options[:project_root]}/templates/#{path}" + def load_variables + load_variables_folder("base") + load_variables_folder(LONO_ENV) end - def variables(vars={}) - vars.each do |var,value| - instance_variable_set("@#{var}", value) + # Load the variables defined in config/variables/* to make available in the + # template blocks in config/templates/*. + # + # Example: + # + # `config/variables/base/variables.rb`: + # @foo = 123 + # + # `config/templates/base/resources.rb`: + # template "mytemplate.yml" do + # source "mytemplate.yml.erb" + # variables(foo: @foo) + # end + # + # NOTE: Only able to make instance variables avaialble with instance_eval + # Wasnt able to make local variables available. + def load_variables_folder(folder) + paths = Dir.glob("#{@_config_path}/variables/#{folder}/**/*") + paths.select{ |e| File.file? e }.each do |path| + instance_eval(IO.read(path)) end end - def partial(path,vars={}, options={}) - path = "#{@options[:project_root]}/templates/partial/#{path}" - template = IO.read(path) - variables(vars) - result = erb_result(path, template) - result = indent(result, options[:indent]) if options[:indent] - result + # Load custom helper methods from the user's infra repo + def load_custom_helpers + Dir.glob("#{@_project_root}/helpers/**/*_helper.rb").each do |path| + filename = path.sub(%r{.*/},'').sub('.rb','') + module_name = filename.classify + + require path + self.class.send :include, module_name.constantize + end + end - # add indentation - def indent(result, indentation_amount) - result.split("\n").map do |line| - " " * indentation_amount + line - end.join("\n") + def source(path) + @_source = path[0..0] == '/' ? path : "#{@_project_root}/templates/#{path}" + @_source += ".#{@_detected_format}" end + def variables(vars={}) + vars.each do |var,value| + instance_variable_set("@#{var}", value) + end + end + def erb_result(path, template) begin ERB.new(template, nil, "-").result(binding) rescue Exception => e puts e + puts e.backtrace if ENV['DEBUG'] # how to know where ERB stopped? - https://www.ruby-forum.com/topic/182051 # syntax errors have the (erb):xxx info in e.message # undefined variables have (erb):xxx info in e.backtrac error_info = e.message.split("\n").grep(/\(erb\)/)[0] error_info ||= e.backtrace.grep(/\(erb\)/)[0] raise unless error_info # unable to find the (erb):xxx: error line line = error_info.split(':')[1].to_i - puts "Error evaluating ERB template on line #{line.to_s.colorize(:red)} of: #{path.sub(/^\.\//, '')}" + puts "Error evaluating ERB template on line #{line.to_s.colorize(:red)} of: #{path.sub(/^\.\//, '').colorize(:green)}" template_lines = template.split("\n") context = 5 # lines of context top, bottom = [line-context-1, 0].max, line+context-1 spacing = template_lines.size.to_s.size @@ -73,52 +112,10 @@ end exit 1 unless ENV['TEST'] end end - def user_data(path, vars={}) - path = "#{@options[:project_root]}/templates/user_data/#{path}" - template = IO.read(path) - variables(vars) - result = erb_result(path, template) - output = [] - result.split("\n").each do |line| - output += transform(line) - end - json = output.to_json - json[0] = '' # remove first char: [ - json.chop! # remove last char: ] - end - - def ref(name) - %Q|{"Ref"=>"#{name}"}| - end - - def find_in_map(*args) - %Q|{"Fn::FindInMap" => [ #{transform_array(args)} ]}| - end - - def base64(value) - %Q|{"Fn::Base64"=>"#{value}"}| - end - - def get_att(*args) - %Q|{"Fn::GetAtt" => [ #{transform_array(args)} ]}| - end - - def get_azs(region="AWS::Region") - %Q|{"Fn::GetAZs"=>"#{region}"}| - end - - def join(delimiter, values) - %Q|{"Fn::Join" => ["#{delimiter}", [ #{transform_array(values)} ]]}| - end - - def select(index, list) - %Q|{"Fn::Select" => ["#{index}", [ #{transform_array(list)} ]]}| - end - def transform_array(arr) arr.map! {|x| x =~ /=>/ ? x : x.inspect } arr.join(',') end @@ -245,7 +242,11 @@ # For simple just parameters files that can also be generated with lono, the CFN # Fn::Base64 function is not available and as lono is not being used in the context # of CloudFormation. So this can be used in it's place. def encode_base64(text) Base64.strict_encode64(text).strip + end + + def name + @_name end end