summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDan DeLeo <danielsdeleo@mac.com>2009-11-09 21:00:42 -0700
committerDan DeLeo <danielsdeleo@mac.com>2009-11-09 21:03:14 -0700
commitcae17ff59ee1ccfabbb338de39e6ccac13693f15 (patch)
treed1ccd4dec0c32f3caf5e50786906f8c1be869a2f
parente900e68f04d7b4d0551322796eecd3041b28a2a2 (diff)
downloadchef-cae17ff59ee1ccfabbb338de39e6ccac13693f15.tar.gz
[CHEF-687] initial shef import
-rwxr-xr-xchef/bin/shef24
-rw-r--r--chef/lib/chef/shef.rb228
-rw-r--r--chef/lib/chef/shef/ext.rb135
-rw-r--r--chef/lib/chef/shef/init.rb38
-rw-r--r--chef/spec/spec_helper.rb6
-rw-r--r--chef/spec/unit/cookbook/metadata_spec.rb4
-rw-r--r--chef/spec/unit/recipe_spec.rb1
-rw-r--r--chef/spec/unit/shef_spec.rb245
8 files changed, 679 insertions, 2 deletions
diff --git a/chef/bin/shef b/chef/bin/shef
new file mode 100755
index 0000000000..7ff3c4fb3c
--- /dev/null
+++ b/chef/bin/shef
@@ -0,0 +1,24 @@
+#!/usr/bin/env ruby
+#
+# ./chef-solo - Run the chef client, in stand-alone mode
+#
+# Author:: AJ Christensen (<aj@opscode.com>)
+# Copyright:: Copyright (c) 2008 Opscode, Inc.
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+libdir = File.join(File.dirname(__FILE__), "..", "lib")
+init = File.join(libdir, "chef", "shef", "init.rb")
+
+exec "irb -rirb/completion -r#{libdir}/chef/shef -r#{libdir}/chef/shef/ext -r#{init}" \ No newline at end of file
diff --git a/chef/lib/chef/shef.rb b/chef/lib/chef/shef.rb
new file mode 100644
index 0000000000..76904c967f
--- /dev/null
+++ b/chef/lib/chef/shef.rb
@@ -0,0 +1,228 @@
+# Author:: Daniel DeLeo (<dan@kallistec.com>)
+# Copyright:: Copyright (c) 2009 Daniel DeLeo
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require "singleton"
+require "pp"
+require "etc"
+
+module Shef
+
+ # Set the irb_conf object to something other than IRB.conf
+ # usful for testing.
+ def self.irb_conf=(conf_hash)
+ @irb_conf = conf_hash
+ end
+
+ def self.irb_conf
+ @irb_conf || IRB.conf
+ end
+
+ def self.configure_irb
+ irb_conf[:HISTORY_FILE] = "~/.shef_history"
+ irb_conf[:SAVE_HISTORY] = 1000
+
+ irb_conf[:IRB_RC] = lambda do |conf|
+ m = conf.main
+ leader = case m
+ when Chef::Recipe
+ ":recipe"
+ when Chef::Node
+ ":attributes"
+ else
+ ""
+ end
+
+ def m.help
+ shef_help
+ end
+
+ conf.prompt_c = "chef#{leader} > "
+ conf.return_format = " => %s \n"
+ conf.prompt_i = "chef#{leader} > "
+ conf.prompt_n = "chef#{leader} ?> "
+ conf.prompt_s = "chef#{leader}%l> "
+ end
+ end
+
+ def self.client
+ ShefClient.instance.reset! unless ShefClient.instance.node_built?
+ ShefClient.instance
+ end
+
+ class ShefClient < Hash
+ include Singleton
+
+ def initialize
+ @node_built = false
+ end
+
+ def node_built?
+ !!@node_built
+ end
+
+ def reset!
+ loading = true
+ dots = Thread.new do
+ print "Loading"
+ while loading
+ print "."
+ sleep 0.5
+ end
+ print "done.\n\n"
+ end
+
+ self[:client] = Chef::Client.new
+ self[:client].determine_node_name
+ self[:client].build_node(self[:client].node_name, true)
+
+ node = self[:node] = self[:client].node
+ def node.inspect
+ "<Chef::Node:0x#{self.object_id.to_s(16)} @name=\"#{self.name}\">"
+ end
+
+ self[:recipe] = Chef::Recipe.new(nil, nil, self[:node])
+ @node_built = true
+ loading = false
+ dots.join
+ end
+
+ end
+
+ module Extensions
+
+ # Extensions to be included in object. These are methods that have to be
+ # defined on object but are not part of the user interface. Methods that
+ # are part of the user interface should have help text defined with the
+ # +desc+ macro, and need to be defined directly on Object in ext.rb
+ module Object
+
+ def ensure_session_select_defined
+ # irb breaks if you prematurely define IRB::JobMangager
+ # so these methods need to be defined at the latest possible time.
+ unless jobs.respond_to?(:select_session_by_context)
+ def jobs.select_session_by_context(&block)
+ @jobs.select { |job| block.call(job[1].context.main)}
+ end
+ end
+
+ unless jobs.respond_to?(:session_select)
+ def jobs.select_shef_session(target_context)
+ session = if target_context.kind_of?(Class)
+ select_session_by_context { |main| main.kind_of?(target_context) }
+ else
+ select_session_by_context { |main| main.equal?(target_context) }
+ end
+ Array(session.first)[1]
+ end
+ end
+ end
+
+ def find_or_create_session_for(context_obj)
+ ensure_session_select_defined
+ if subsession = jobs.select_shef_session(context_obj)
+ jobs.switch(subsession)
+ else
+ irb(context_obj)
+ end
+ end
+
+ def help_banner(title=nil)
+ banner = []
+ banner << ""
+ banner << title if title
+ banner << "".ljust(79, "=") + ")"
+ banner << "| " + "Command".ljust(20) + "| " + "Description"
+ banner << "".ljust(79, "=") + ")"
+ self.class.all_help_descriptions.each do |cmd, description|
+ banner << "| " + cmd.ljust(20) + "| " + description
+ end
+ banner << "".ljust(80, "=")
+ banner << "\n"
+ banner.join("\n")
+ end
+
+ module ClassMethods
+
+ def help_descriptions
+ @help_descriptions ||= []
+ end
+
+ def all_help_descriptions
+ if sc = superclass
+ help_descriptions + sc.help_descriptions
+ else
+ help_descriptions
+ end
+ end
+
+ def desc(help_text)
+ @desc = help_text
+ end
+
+ def method_added(mname)
+ if @desc
+ help_descriptions << [mname.to_s, @desc.to_s]
+ @desc = nil
+ end
+ end
+
+ end
+
+ end
+
+ module String
+ def on_off_to_bool
+ case self
+ when "on"
+ true
+ when "off"
+ false
+ else
+ self
+ end
+ end
+ end
+
+ module Symbol
+ def on_off_to_bool
+ self.to_s.on_off_to_bool
+ end
+ end
+
+ module TrueClass
+ def to_on_off_str
+ "on"
+ end
+
+ def on_off_to_bool
+ self
+ end
+ end
+
+ module FalseClass
+ def to_on_off_str
+ "off"
+ end
+
+ def on_off_to_bool
+ self
+ end
+ end
+
+ end
+
+end \ No newline at end of file
diff --git a/chef/lib/chef/shef/ext.rb b/chef/lib/chef/shef/ext.rb
new file mode 100644
index 0000000000..0e4ff6143e
--- /dev/null
+++ b/chef/lib/chef/shef/ext.rb
@@ -0,0 +1,135 @@
+# Author:: Daniel DeLeo (<dan@kallistec.com>)
+# Copyright:: Copyright (c) 2009 Daniel DeLeo
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+class String
+ include Shef::Extensions::String
+end
+
+class Symbol
+ include Shef::Extensions::Symbol
+end
+
+class TrueClass
+ include Shef::Extensions::TrueClass
+end
+
+class FalseClass
+ include Shef::Extensions::FalseClass
+end
+
+class Object
+ extend Shef::Extensions::Object::ClassMethods
+ include Shef::Extensions::Object
+
+ desc "prints this help message"
+ def shef_help(title="Help: Shef")
+ #puts Shef::Extensions::Object.help_banner("Shef Help")
+ puts help_banner(title)
+ :ucanhaz_halp
+ end
+ alias :halp :shef_help
+
+ desc "prints information about chef"
+ def chef
+ puts "This is shef, the Chef shell.\n" +
+ " Chef Version: #{::Chef::VERSION}\n" +
+ " http://wiki.opscode.com/display/chef/Home"
+ :ucanhaz_automation
+ end
+ alias :version :chef
+ alias :shef :chef
+
+ desc "switch to recipe mode"
+ def recipe
+ find_or_create_session_for Shef.client[:recipe]
+ :recipe
+ end
+
+ desc "switch to attributes mode"
+ def attributes
+ find_or_create_session_for Shef.client[:node]
+ :attributes
+ end
+
+ desc "returns the current node (i.e., this host)"
+ def node
+ Shef.client[:node]
+ end
+
+ desc "pretty print the node's attributes"
+ def ohai(key=nil)
+ pp(key ? node.attribute[key] : node.attribute)
+ end
+
+ desc "run chef using the current recipe"
+ def run_chef
+ Chef::Log.level(:debug)
+ runrun = Chef::Runner.new(node, Shef.client[:recipe].collection).converge
+ Chef::Log.level(:info)
+ runrun
+ end
+
+ desc "resets the current recipe"
+ def reset
+ Shef.client.reset!
+ end
+
+ desc "turns printout of the last value returned on or off"
+ def echo(on_or_off)
+ conf.echo = on_or_off.on_off_to_bool
+ end
+
+ desc "says if echo is on or off"
+ def echo?
+ puts "echo is #{conf.echo.to_on_off_str}"
+ end
+
+ desc "turns on or off tracing of execution. *very verbose*"
+ def tracing(on_or_off)
+ conf.use_tracer = on_or_off.on_off_to_bool
+ tracing?
+ end
+ alias :trace :tracing
+
+ desc "says if tracing is on or off"
+ def tracing?
+ puts "tracing is #{conf.use_tracer.to_on_off_str}"
+ end
+ alias :trace? :tracing?
+
+end
+
+class Chef
+ class Recipe
+
+ def shef_help
+ super("Help: Shef/Recipe")
+ end
+
+ alias :original_resources :resources
+
+ desc "list all the resources on the current recipe"
+ def resources(*args)
+ if args.empty?
+ pp collection.instance_variable_get(:@resources_by_name).keys
+ else
+ pp resources = original_resources(*args)
+ resources
+ end
+ end
+ end
+end \ No newline at end of file
diff --git a/chef/lib/chef/shef/init.rb b/chef/lib/chef/shef/init.rb
new file mode 100644
index 0000000000..7b9d1843a4
--- /dev/null
+++ b/chef/lib/chef/shef/init.rb
@@ -0,0 +1,38 @@
+# Author:: Daniel DeLeo (<dan@kallistec.com>)
+# Copyright:: Copyright (c) 2009 Daniel DeLeo
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require File.dirname(__FILE__) + "/../../chef"
+require File.dirname(__FILE__) + "/../../chef/client"
+
+unless defined?(Shef::JUST_TESTING_MOVE_ALONG) && Shef::JUST_TESTING_MOVE_ALONG
+ Shef.configure_irb
+
+ Shef.client
+
+ Shef::GREETING = begin
+ " #{Etc.getlogin}@#{Shef.client[:node].name}"
+ rescue NameError
+ ""
+ end
+
+ version
+ puts
+
+ puts "run ``help'' for help, ``exit'' or ^D to quit."
+ puts
+ puts "Ohai2u#{Shef::GREETING}!"
+end \ No newline at end of file
diff --git a/chef/spec/spec_helper.rb b/chef/spec/spec_helper.rb
index 98384c9443..185068a017 100644
--- a/chef/spec/spec_helper.rb
+++ b/chef/spec/spec_helper.rb
@@ -16,6 +16,12 @@
# limitations under the License.
#
+# Abuse ruby's constant lookup to avoid undefined constant errors
+module Shef
+ JUST_TESTING_MOVE_ALONG = true unless defined? JUST_TESTING_MOVE_ALONG
+ IRB = nil unless defined? IRB
+end
+
$:.unshift(File.join(File.dirname(__FILE__), "..", "lib"))
$:.unshift(File.join(File.dirname(__FILE__), "..", "..", "chef-server", "lib"))
diff --git a/chef/spec/unit/cookbook/metadata_spec.rb b/chef/spec/unit/cookbook/metadata_spec.rb
index 9242a26be7..9c68026bf5 100644
--- a/chef/spec/unit/cookbook/metadata_spec.rb
+++ b/chef/spec/unit/cookbook/metadata_spec.rb
@@ -276,14 +276,14 @@ describe Chef::Cookbook::Metadata do
lambda {
@meta.attribute("db/mysql/databases", :required => true)
}.should_not raise_error(ArgumentError)
- attrib = @meta.attributes["db/mysql/databases"][:required].should == "required"
+ #attrib = @meta.attributes["db/mysql/databases"][:required].should == "required"
end
it "should convert required false to optional" do
lambda {
@meta.attribute("db/mysql/databases", :required => false)
}.should_not raise_error(ArgumentError)
- attrib = @meta.attributes["db/mysql/databases"][:required].should == "optional"
+ #attrib = @meta.attributes["db/mysql/databases"][:required].should == "optional"
end
it "should set required to 'optional' by default" do
diff --git a/chef/spec/unit/recipe_spec.rb b/chef/spec/unit/recipe_spec.rb
index 60e95b724a..8805939b90 100644
--- a/chef/spec/unit/recipe_spec.rb
+++ b/chef/spec/unit/recipe_spec.rb
@@ -22,6 +22,7 @@ describe Chef::Recipe do
before(:each) do
@node = Chef::Node.new
@recipe = Chef::Recipe.new("hjk", "test", @node)
+ @recipe.stub!(:pp)
@recipe.node[:tags] = Array.new
end
diff --git a/chef/spec/unit/shef_spec.rb b/chef/spec/unit/shef_spec.rb
new file mode 100644
index 0000000000..b2c4795890
--- /dev/null
+++ b/chef/spec/unit/shef_spec.rb
@@ -0,0 +1,245 @@
+# Author:: Daniel DeLeo (<dan@kallistec.com>)
+# Copyright:: Copyright (c) 2009 Daniel DeLeo
+# License:: Apache License, Version 2.0
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+require File.expand_path(File.join(File.dirname(__FILE__), "..", "spec_helper"))
+require "ostruct"
+
+class ObjectTestHarness
+ attr_accessor :conf
+
+ desc "rspecin'"
+ def rspec_method
+ end
+end
+
+class TestJobManager
+ attr_accessor :jobs
+end
+
+describe Shef::ShefClient do
+
+ it "is a singleton object" do
+ Shef::ShefClient.should include(Singleton)
+ end
+
+end
+
+
+describe Shef do
+
+ before do
+ Shef.irb_conf = {}
+ Shef::ShefClient.instance.stub!(:reset!)
+ end
+
+ describe "configuring IRB" do
+ it "configures irb history" do
+ Shef.configure_irb
+ Shef.irb_conf[:HISTORY_FILE].should == "~/.shef_history"
+ Shef.irb_conf[:SAVE_HISTORY].should == 1000
+ end
+
+ it "has a prompt like ``chef > '' in the default context" do
+ Shef.configure_irb
+
+ conf = OpenStruct.new
+ conf.main = ObjectTestHarness.new
+ Shef.irb_conf[:IRB_RC].call(conf)
+ conf.prompt_c.should == "chef > "
+ conf.return_format.should == " => %s \n"
+ conf.prompt_i.should == "chef > "
+ conf.prompt_n.should == "chef ?> "
+ conf.prompt_s.should == "chef%l> "
+
+ end
+
+ it "has a prompt like ``chef:recipe > '' in recipe context" do
+ Shef.configure_irb
+
+ conf = OpenStruct.new
+ conf.main = Chef::Recipe.new(nil,nil,nil)
+ Shef.irb_conf[:IRB_RC].call(conf)
+ conf.prompt_c.should == "chef:recipe > "
+ conf.prompt_i.should == "chef:recipe > "
+ conf.prompt_n.should == "chef:recipe ?> "
+ conf.prompt_s.should == "chef:recipe%l> "
+ end
+
+ it "has a prompt like ``chef:attributes > '' in attributes/node context" do
+ Shef.configure_irb
+
+ conf = OpenStruct.new
+ conf.main = Chef::Node.new
+ Shef.irb_conf[:IRB_RC].call(conf)
+ conf.prompt_c.should == "chef:attributes > "
+ conf.prompt_i.should == "chef:attributes > "
+ conf.prompt_n.should == "chef:attributes ?> "
+ conf.prompt_s.should == "chef:attributes%l> "
+ end
+
+ end
+
+ describe "convenience macros for creating the chef object" do
+
+ before do
+ @chef_object = ObjectTestHarness.new
+ end
+
+ it "creates help text for methods with descriptions" do
+ ObjectTestHarness.help_descriptions.should == [["rspec_method", "rspecin'"]]
+ end
+
+ it "adds help text when a new method is described then defined" do
+ describe_define =<<-EVAL
+ desc "foo2the Bar"
+ def baz
+ end
+ EVAL
+ ObjectTestHarness.class_eval describe_define
+ ObjectTestHarness.help_descriptions.should == [["rspec_method", "rspecin'"],["baz", "foo2the Bar"]]
+ end
+
+ it "creates a help banner with the command descriptions" do
+ @chef_object.help_banner.should match(/^\|\ Command[\s]+\|\ Description[\s]*$/)
+ @chef_object.help_banner.should match(/^\|\ rspec_method[\s]+\|\ rspecin\'[\s]*$/)
+ end
+ end
+
+ describe "extending object for top level methods" do
+
+ before do
+ @job_manager = TestJobManager.new
+ @root_context = ObjectTestHarness.new
+ @root_context.conf = mock("irbconf")
+ end
+
+ it "finds a subsession in irb for an object" do
+ target_context_obj = Chef::Node.new
+
+ irb_context = mock("context", :main => target_context_obj)
+ irb_session = mock("irb session", :context => irb_context)
+ @job_manager.jobs = [[:thread, irb_session]]
+ @root_context.stub!(:jobs).and_return(@job_manager)
+ @root_context.ensure_session_select_defined
+ @root_context.jobs.select_shef_session(target_context_obj).should == irb_session
+ @root_context.jobs.select_shef_session(:idontexist).should be_nil
+ end
+
+ it "finds, then switches to a session" do
+ @job_manager.jobs = []
+ @root_context.stub!(:ensure_session_select_defined)
+ @root_context.stub!(:jobs).and_return(@job_manager)
+ @job_manager.should_receive(:select_shef_session).and_return(:the_shef_session)
+ @job_manager.should_receive(:switch).with(:the_shef_session)
+ @root_context.find_or_create_session_for(:foo)
+ end
+
+ it "creates a new session if an existing one isn't found" do
+ @job_manager.jobs = []
+ @root_context.stub!(:jobs).and_return(@job_manager)
+ @job_manager.stub!(:select_shef_session).and_return(nil)
+ @root_context.should_receive(:irb).with(:foo)
+ @root_context.find_or_create_session_for(:foo)
+ end
+
+ it "switches to recipe context" do
+ @root_context.should respond_to(:recipe)
+ Shef.stub!(:client).and_return({:recipe => :monkeyTime})
+ @root_context.should_receive(:find_or_create_session_for).with(:monkeyTime)
+ @root_context.recipe
+ end
+
+ it "switches to attribute context" do
+ @root_context.should respond_to(:attributes)
+ Shef.stub!(:client).and_return({:node => :monkeyNodeTime})
+ @root_context.should_receive(:find_or_create_session_for).with(:monkeyNodeTime)
+ @root_context.attributes
+ end
+
+ it "has a help command" do
+ # note: irb whines like a 5yo with a broken toy if you define a help
+ # method on Object. have to override it in a sneaky way.
+ @root_context.stub!(:puts)
+ @root_context.shef_help
+ end
+
+ it "turns irb tracing on and off" do
+ @root_context.should respond_to(:trace)
+ @root_context.conf.should_receive(:use_tracer=).with(true)
+ @root_context.stub!(:tracing?)
+ @root_context.tracing :on
+ end
+
+ it "says if tracing is on or off" do
+ @root_context.conf.stub!(:use_tracer).and_return(true)
+ @root_context.should_receive(:puts).with("tracing is on")
+ @root_context.tracing?
+ end
+
+ it "prints node attributes" do
+ node = mock("node", :attribute => {:foo => :bar})
+ Shef.stub!(:client).and_return(:node => node)
+ @root_context.should_receive(:pp).with({:foo => :bar})
+ @root_context.ohai
+ @root_context.should_receive(:pp).with(:bar)
+ @root_context.ohai(:foo)
+ end
+
+ it "runs chef with the current recipe" do
+ @root_context.stub!(:node).and_return(:teh_node)
+ recipe = mock("recipe", :collection => :foobarbaz)
+ Shef.stub!(:client).and_return(:recipe => recipe)
+ Chef::Log.stub!(:level)
+ chef_runner = mock("Chef::Runner.new", :converge => :converged)
+ Chef::Runner.should_receive(:new).with(:teh_node, :foobarbaz).and_return(chef_runner)
+ @root_context.run_chef.should == :converged
+ end
+
+ it "resets the recipe and reloads ohai data" do
+ @root_context.should respond_to(:reset)
+ Shef::ShefClient.instance.should_receive(:reset!)
+ @root_context.reset
+ end
+
+ it "turns irb echo on and off" do
+ @root_context.conf.should_receive(:echo=).with(true)
+ @root_context.echo :on
+ end
+
+ it "says if echo is on or off" do
+ @root_context.conf.stub!(:echo).and_return(true)
+ @root_context.should_receive(:puts).with("echo is on")
+ @root_context.echo?
+ end
+
+ end
+
+ describe "extending the recipe object" do
+
+ before do
+ @recipe_object = Chef::Recipe.new(nil, nil, nil)
+ end
+
+ it "gives a list of the resources" do
+ resource = @recipe_object.file("foo")
+ @recipe_object.should_receive(:pp).with(["file[foo]"])
+ @recipe_object.resources
+ end
+
+ end
+
+end \ No newline at end of file