module Poise::Helpers::TemplateContent::ClassMethods

@!classmethods

Public Instance Methods

attribute(name, options={}) click to toggle source
Calls superclass method
# File lib/poise/helpers/template_content.rb, line 38
def attribute(name, options={})
  if options.delete(:template)
    name_prefix = name.empty? ? '' : "#{name}_"

    # If you are reading this, I'm so sorry
    # This is used for computing the default cookbook below
    parent_filename = caller.first.reverse.split(':', 4).last.reverse

    # If our parent class also declared a template_content attribute on the same name, inherit its options
    if superclass.respond_to?("_#{name_prefix}_template_content_options")
      options = superclass.send("_#{name_prefix}_template_content_options").merge(options)
    end

    # Template source path if using a template
    attribute("#{name_prefix}source", kind_of: String)
    define_method("_#{name_prefix}source") do
      send("#{name_prefix}source") || maybe_eval(options[:default_source])
    end

    # Template cookbook name if using a template
    attribute("#{name_prefix}cookbook", kind_of: [String, Symbol], default: lazy do
      if send("#{name_prefix}source")
        cookbook_name
      elsif options[:default_cookbook]
        maybe_eval(options[:default_cookbook])
      else
        Poise::Utils.find_cookbook_name(run_context, parent_filename)
      end
    end)

    # Template variables if using a template
    attribute("#{name_prefix}options", option_collector: true)

    # Make an alias for #variables to match the template resource.
    alias_method("#{name_prefix}variables", "#{name_prefix}options")

    # The big one, get/set content, but if you are getting and no
    # explicit content was given, try to render the template
    define_method("#{name_prefix}content") do |arg=nil, no_compute=false|
      ret = set_or_return("#{name_prefix}content", arg, kind_of: String)
      if !ret && !arg && !no_compute
        ret = send("_#{name_prefix}content")
        # Cache the results for next time
        set_or_return("#{name_prefix}content", ret, {}) if ret
      end
      ret
    end

    # Validate that arguments work
    define_method("_#{name_prefix}validate") do
      if options[:required] && !send("_#{name_prefix}source") && !send("#{name_prefix}content", nil, true)
        raise Chef::Exceptions::ValidationFailed, "#{self}: One of #{name_prefix}source or #{name_prefix}content is required"
      end
      if send("#{name_prefix}source") && send("#{name_prefix}content", nil, true)
        raise Chef::Exceptions::ValidationFailed, "#{self}: Only one of #{name_prefix}source or #{name_prefix}content can be specified"
      end
    end

    # Monkey patch #after_create to run best-effort validation. Arguments
    # could be changed after creation, but this gives nicer errors for
    # most cases.
    unless options[:no_validate_on_create]
      old_after_created = instance_method(:after_created)
      define_method(:after_created) do
        old_after_created.bind(self).call
        send("_#{name_prefix}validate") if Array(action) == Array(self.class.default_action)
      end
    end

    # Compile the needed content
    define_method("_#{name_prefix}content") do
      # Run validation again
      send("_#{name_prefix}validate")
      # Get all the relevant parameters
      content = send("#{name_prefix}content", nil, true)
      source = send("_#{name_prefix}source")
      if content
        content # I don't think it can ever hit this branch
      elsif source
        cookbook = send("#{name_prefix}cookbook")
        template_options = send("#{name_prefix}options")
        send("_#{name_prefix}render_template", source, cookbook, template_options)
      else
        maybe_eval(options[:default])
      end
    end

    # Actually render a template
    define_method("_#{name_prefix}render_template") do |source, cookbook, template_options|
      all_template_options = {}
      all_template_options.update(maybe_eval(options[:default_options])) if options[:default_options]
      all_template_options.update(template_options)
      all_template_options[:new_resource] = self
      finder = Chef::Provider::TemplateFinder.new(run_context, cookbook, node)
      context = Chef::Mixin::Template::TemplateContext.new(all_template_options)
      context[:node] = node
      context[:template_finder] = finder
      context.render_template(finder.find(source))
    end

    # Used to check if a parent class already defined a template_content thing here
    define_singleton_method("_#{name_prefix}_template_content_options") do
      options
    end
  else
    super if defined?(super)
  end
end
included(klass) click to toggle source
Calls superclass method
# File lib/poise/helpers/template_content.rb, line 147
def included(klass)
  super
  klass.extend(ClassMethods)
end