2

I'm a bit of a Ruby-newbie, but I'm trying to render Puppet .erb templates using a script and a load of synthesised data (which I'll put in a Yaml file or something). Our puppet templates are mostly along these sorts of lines:

# Some config file
<%= @some_ordinary_variable %>
<%= some_other_variable %>
<%= @something_undefined %>
<%= scope.function_hiera("some_hiera_variable") %>

I've got as far as mocking up the hiera lookups, and found Problem using OpenStruct with ERB as a way to substitute in the "some_other_variable" (I'm a bit stuck on getting "@some_ordinary_variable" to work, but I think I can figure that one out.

What I'm asking about is how can I use a binding that lets me run a bit of code with each variable lookup? I'm thinking I'd like to run something like:

def variable_lookup(key)
  if @variables.has_key?(key)
    return @variables[key]
  else
    warn "The variable " + key + " is required by the template but not set"
  end
end

I could then merge this with my Hiera mock-up to lookup the Hiera data. The code I have so far is:

require 'rubygems'
require 'yaml'
require 'erb'
require 'ostruct'

class ErbBinding < OpenStruct
  include Enumerable
  attr_accessor :yamlfile, :hiera_config, :erbfile, :yaml

  def scope
    self
  end

  def function_hiera(key)
    val = @yaml['values'][key]
    if val.is_a?(YAML::Syck::Scalar)
      return val.value
    else
      warn "erby: " + key + " required in template but not set in yaml"
      return nil
    end
  end

  def get_binding
    return binding()
  end
end

variables = {'some_other_variable' => 'hello world'}

hs = ErbBinding.new(variables)
template = File.read('./template.erb')
hs.yaml = YAML.parse( File.read('./data.yaml') )

erb = ERB.new(template)

vars_binding = hs.send(:get_binding)
puts erb.result(vars_binding)

I can't figure out how to set up a binding that runs code, rather than just using the 'variables' hash. Any ideas?

Community
  • 1
  • 1
Ralph Bolton
  • 774
  • 7
  • 14

1 Answers1

1

This turned out to be surprisingly simple! I found that you can use the special method "method_missing" to scoop up all method calls that aren't defined. That is, we're using the binding to make a hash into an object (with each hash key becoming a method in the object). If there's a missing method (ie. no key set in the hash), then Ruby calls "method_missing" - all I've done is got that to say what the method name was that is missing. If found the important bit of info here: http://ruby-metaprogramming.rubylearning.com/html/ruby_metaprogramming_2.html

If you're wondering, here's the total code I have so far...

require 'rubygems'
require 'yaml'
require 'erb'
require 'ostruct'

class ErbBinding < OpenStruct
  include Enumerable
  attr_accessor :yamlfile, :hiera_config, :erbfile, :yaml

  def method_missing(m, *args, &block)
    warn "erby: Variable #{m} required but not set in yaml"
  end

  def scope
    self
  end

  def function_hiera(key)
    val = @yaml['values'][key]
    if val.is_a?(YAML::Syck::Scalar)
      return val.value
    else
      warn "erby: " + key + " required in template but not set in yaml"
      return nil
    end
  end

  def get_binding
    return binding()
  end
end

variables = {'some_other_variable' => 'hello world'}

hs = ErbBinding.new(variables)
template = File.read('./template.erb')
hs.yaml = YAML.parse( File.read('./data.yaml') )

erb = ERB.new(template)

vars_binding = hs.send(:get_binding)
puts erb.result(vars_binding)

This code still won't handle a template like <%= @variable %>, but it'll handle the other two cases in my original question.

Ralph Bolton
  • 774
  • 7
  • 14