Search code examples
chef-recipe

Using __END__ and DATA in Chef recipes (to run legacy shell scripts)


I'm migrating some shell scripts to Chef recipes. Some of these scripts are fairly involved, so just to make life easier in the short term and to avoid introducing bugs in rewriting everything in Chef/Ruby, I'd like to just run some of them as-is. They're all well-written and idempotent, so honestly there's no rush, but of course, the eventual goal is to rewrite them.

One cool feature of Ruby is its __END__ keyword/method: Lines below __END__ will not be executed. Those lines will be available via the special filehandle DATA.

It would be cool to ship the shell scripts as-is inside the the recipe after __END__, maybe something like the following, which I placed in chef-repo/cookbooks/ruby-data-test/recipes/default.rb:

file = Tempfile.new(File.basename(__FILE__))
file << DATA.read
bash file.path
file.unlink
__END__
echo "Hello, world"

However when I run this (with chef-solo -c solo.rb --override-runlist 'recipe[ruby-data-test]'), I get the following error:

[2014-10-03T17:14:56+00:00] ERROR: uninitialized constant Chef::Recipe::DATA

I'm pretty new to Chef, but I'm guessing the above is something about Chef wrapping my recipe in a class, and there's something simple preventing me from accessing DATA. Since it's "global" (?) I tried putting a dollar sign ($DATA) in front of it but that failed with:

NoMethodError
-------------
undefined method `read' for nil:NilClass

So the question is: How do I access DATA in my Chef recipe? Thanks!


Solution

  • It appears you don't have access to DATA, but you can fake it by reading in the current file yourself and splitting on __END__, like Sinatra does.

    I ended up making a Chef LWRP for reuse. I don't know if I'll actually end up using this, but I wanted to figure it out. Like I said, I'm a Chef/Ruby noob, so any better ideas or suggestions welcome!

    ruby_data_test/recipes/default.rb:

    ruby_data_test_execute_ruby_data __FILE__
    
    __END__
    #!/bin/bash
    set -o errexit
    date
    echo "Hello, world"
    

    ruby_data_test/resources/execute_ruby_data.rb:

    actions :execute_ruby_data
    default_action :execute_ruby_data
    
    attribute :source, :name_attribute => true, :required => true
    attribute :args, :kind_of => Array
    attribute :ignore_errors, :kind_of => [TrueClass, FalseClass], :default => false
    

    ruby_data_test/providers/execute_ruby_data.rb:

    def whyrun_supported?
        true
    end
    
    use_inline_resources
    
    action :execute_ruby_data do
        converge_by("Executing #{@new_resource}") do
            Chef::Log.info("Executing #{@new_resource}")
    
            file_who_called_me = @new_resource.source
    
            io = ::IO.respond_to?(:binread) ? ::IO.binread(file_who_called_me) : ::IO.read(file_who_called_me)
            app, data = io.gsub("\r\n", "\n").split(/^__END__$/, 2)
            data.lstrip!
    
            file = Tempfile.new('execute_ruby_data')
            file << data
            file.chmod(0755)
            file.close
    
            exit_status =  ::Open3.popen2e(file.path, *@new_resource.args) do |stdin, stdout_and_stderr, wait_thr|
                stdout_and_stderr.each { |line| puts line }
                wait_thr.value # exit status
            end
            if exit_status != 0 && !@new_resource.ignore_errors
                throw RuntimeError
            end
    
        end
    end
    

    Here's the output:

    $ chef-solo -c solo.rb --override-runlist 'recipe[ruby_data_test]'  
    Starting Chef Client, version 11.12.4
    [2014-10-03T21:50:29+00:00] WARN: Run List override has been provided.
    [2014-10-03T21:50:29+00:00] WARN: Original Run List: []
    [2014-10-03T21:50:29+00:00] WARN: Overridden Run List: [recipe[ruby_data_test]]
    Compiling Cookbooks...
    Converging 1 resources
    Recipe: ruby_data_test::default
      * ruby_data_test_execute_ruby_data[/root/chef/chef-repo/cookbooks/ruby_data_test/recipes/default.rb] action execute_ruby_dataFri Oct  3 21:50:29 UTC 2014
    Hello, world
    
        - Executing ruby_data_test_execute_ruby_data[/root/chef/chef-repo/cookbooks/ruby_data_test/recipes/default.rb]
    
    
    Running handlers:
    Running handlers complete
    
    Chef Client finished, 1/1 resources updated in 1.387608 seconds