summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBryan Berry <bryan.berry@gmail.com>2012-11-22 11:52:42 +0100
committerdanielsdeleo <dan@opscode.com>2013-01-17 12:19:51 -0800
commitcb05bbb14f54325b384eaec39c90d747cfd61c54 (patch)
treeac85fead30ad0fcf803403371c0cc586942a458e
parent6705514c81550024fe37f62c71d85f101637dedd (diff)
downloadchef-cb05bbb14f54325b384eaec39c90d747cfd61c54.tar.gz
CHEF-3571 add chef-recipe command
-rwxr-xr-xbin/chef-recipe25
-rw-r--r--chef.gemspec2
-rw-r--r--lib/chef/application/recipe.rb160
-rw-r--r--lib/chef/applications.rb1
-rw-r--r--lib/chef/client.rb4
-rw-r--r--spec/unit/application/recipe_spec.rb84
6 files changed, 273 insertions, 3 deletions
diff --git a/bin/chef-recipe b/bin/chef-recipe
new file mode 100755
index 0000000000..b50a83fe72
--- /dev/null
+++ b/bin/chef-recipe
@@ -0,0 +1,25 @@
+#!/usr/bin/env ruby
+#
+# ./chef-recipe - Run a single chef recipe, for educational purposes
+#
+# Author:: Bryan W. Berry (<bryan.berry@gmail.com>)
+# Copyright:: Copyright (c) 2012 Bryan W. Berry
+# 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 'rubygems'
+$:.unshift(File.join(File.dirname(__FILE__), "..", "lib"))
+require 'chef/application/recipe'
+
+Chef::Application::Recipe.new.run
diff --git a/chef.gemspec b/chef.gemspec
index 13d35e66c3..df8757880a 100644
--- a/chef.gemspec
+++ b/chef.gemspec
@@ -32,7 +32,7 @@ Gem::Specification.new do |s|
%w(rspec-core rspec-expectations rspec-mocks).each { |gem| s.add_development_dependency gem, "~> 2.12.0" }
s.bindir = "bin"
- s.executables = %w( chef-client chef-solo knife chef-shell shef )
+ s.executables = %w( chef-client chef-solo knife chef-shell shef chef-recipe )
s.require_path = 'lib'
s.files = %w(Rakefile LICENSE README.md CONTRIBUTING.md) + Dir.glob("{distro,lib,tasks,spec}/**/*")
end
diff --git a/lib/chef/application/recipe.rb b/lib/chef/application/recipe.rb
new file mode 100644
index 0000000000..4e750b0e15
--- /dev/null
+++ b/lib/chef/application/recipe.rb
@@ -0,0 +1,160 @@
+#
+# Author:: Bryan W. Berry (<bryan.berry@gmail.com>)
+# Author:: Daniel DeLeo (<dan@kallistec.com>)
+# Copyright:: Copyright (c) 2012 Bryan W. Berry
+# Copyright:: Copyright (c) 2012 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 'chef'
+require 'chef/application'
+require 'chef/client'
+require 'chef/config'
+require 'chef/log'
+require 'fileutils'
+require 'tempfile'
+require 'chef/providers'
+require 'chef/resources'
+
+class Chef::Application::Recipe < Chef::Application
+
+ banner "Usage: chef-recipe [RECIPE_FILE] [-e RECIPE_TEXT] [-s]"
+
+
+ option :execute,
+ :short => "-e RECIPE_TEXT",
+ :long => "--execute RECIPE_TEXT",
+ :description => "Execute resources supplied in a string",
+ :proc => nil
+
+ option :stdin,
+ :short => "-s",
+ :long => "--stdin",
+ :description => "Execute resources read from STDIN",
+ :boolean => true
+
+ option :log_level,
+ :short => "-l LEVEL",
+ :long => "--log_level LEVEL",
+ :description => "Set the log level (debug, info, warn, error, fatal)",
+ :proc => lambda { |l| l.to_sym }
+
+ option :help,
+ :short => "-h",
+ :long => "--help",
+ :description => "Show this message",
+ :on => :tail,
+ :boolean => true,
+ :show_options => true,
+ :exit => 0
+
+
+ option :version,
+ :short => "-v",
+ :long => "--version",
+ :description => "Show chef version",
+ :boolean => true,
+ :proc => lambda {|v| puts "Chef: #{::Chef::VERSION}"},
+ :exit => 0
+
+ option :why_run,
+ :short => '-W',
+ :long => '--why-run',
+ :description => 'Enable whyrun mode',
+ :boolean => true
+
+ def initialize
+ super
+ end
+
+ def reconfigure
+ configure_logging
+ end
+
+ def read_recipe_file(file_name)
+ recipe_path = file_name
+ unless File.exist?(recipe_path)
+ Chef::Application.fatal!("No file exists at #{recipe_path}", 1)
+ end
+ recipe_path = File.expand_path(recipe_path)
+ recipe_fh = open(recipe_path)
+ recipe_text = recipe_fh.read
+ [recipe_text, recipe_fh]
+ end
+
+ def get_recipe_and_run_context
+ Chef::Config[:solo] = true
+ @chef_client = Chef::Client.new
+ @chef_client.run_ohai
+ @chef_client.load_node
+ @chef_client.build_node
+ run_context = if @chef_client.events.nil?
+ Chef::RunContext.new(@chef_client.node, {})
+ else
+ Chef::RunContext.new(@chef_client.node, {}, @chef_client.events)
+ end
+ recipe = Chef::Recipe.new("(chef-recipe cookbook)", "(chef-recipe recipe)", run_context)
+ [recipe, run_context]
+ end
+
+ # write recipe to temp file, so in case of error,
+ # user gets error w/ context
+ def temp_recipe_file
+ @recipe_fh = Tempfile.open('recipe-temporary-file')
+ @recipe_fh.write(@recipe_text)
+ @recipe_fh.rewind
+ @recipe_filename = @recipe_fh.path
+ end
+
+ def run_chef_recipe
+ if config[:execute]
+ @recipe_text = config[:execute]
+ temp_recipe_file
+ elsif config[:stdin]
+ @recipe_text = STDIN.read
+ temp_recipe_file
+ else
+ @recipe_filename = ARGV[0]
+ @recipe_text,@recipe_fh = read_recipe_file @recipe_filename
+ end
+ recipe,run_context = get_recipe_and_run_context
+ recipe.instance_eval(@recipe_text, @recipe_filename, 1)
+ runner = Chef::Runner.new(run_context)
+ begin
+ runner.converge
+ ensure
+ @recipe_fh.close
+ end
+ end
+
+ def run_application
+ begin
+ parse_options
+ run_chef_recipe
+ Chef::Application.exit! "Exiting", 0
+ rescue SystemExit => e
+ raise
+ rescue Exception => e
+ Chef::Application.debug_stacktrace(e)
+ Chef::Application.fatal!("#{e.class}: #{e.message}", 1)
+ end
+ end
+
+ # Get this party started
+ def run
+ reconfigure
+ run_application
+ end
+
+end
diff --git a/lib/chef/applications.rb b/lib/chef/applications.rb
index 48fd56acf4..36b22ef4f0 100644
--- a/lib/chef/applications.rb
+++ b/lib/chef/applications.rb
@@ -2,3 +2,4 @@ require 'chef/application/agent'
require 'chef/application/client'
require 'chef/application/knife'
require 'chef/application/solo'
+require 'chef/application/recipe'
diff --git a/lib/chef/client.rb b/lib/chef/client.rb
index 0e2ab405b9..db7af934c3 100644
--- a/lib/chef/client.rb
+++ b/lib/chef/client.rb
@@ -129,9 +129,9 @@ class Chef
#--
# TODO: timh/cw: 5-19-2010: json_attribs should be moved to RunContext?
attr_reader :json_attribs
-
attr_reader :run_status
-
+ attr_reader :events
+
# Creates a new Chef::Client.
def initialize(json_attribs=nil, args={})
@json_attribs = json_attribs
diff --git a/spec/unit/application/recipe_spec.rb b/spec/unit/application/recipe_spec.rb
new file mode 100644
index 0000000000..18149c65f1
--- /dev/null
+++ b/spec/unit/application/recipe_spec.rb
@@ -0,0 +1,84 @@
+#
+# Author:: Bryan W. Berry (<bryan.berry@gmail.com>)
+# Copyright:: Copyright (c) 2012 Bryan W. Berry
+# 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 'spec_helper'
+
+describe Chef::Application::Recipe do
+
+ before do
+ @original_config = Chef::Config.configuration
+ @app = Chef::Application::Recipe.new
+ @app.stub!(:configure_logging).and_return(true)
+ @recipe_text = "package 'nyancat'"
+ Chef::Config[:solo] = true
+ end
+
+ after do
+ Chef::Config[:solo] = nil
+ Chef::Config.configuration.replace(@original_config)
+ Chef::Config[:solo] = false
+ end
+
+
+ describe "configuring the application" do
+ it "should set solo mode to true" do
+ @app.reconfigure
+ Chef::Config[:solo].should be_true
+ end
+ end
+ describe "read_recipe_file" do
+ before do
+ @recipe_file_name = "foo.rb"
+ @recipe_path = File.expand_path("foo.rb")
+ @recipe_file = mock("Tempfile (mock)", :read => @recipe_text)
+ @app.stub!(:open).with(@recipe_path).and_return(@recipe_file)
+ File.stub!(:exist?).with("foo.rb").and_return(true)
+ Chef::Application.stub!(:fatal!).and_return(true)
+ end
+ it "should read text properly" do
+ @app.read_recipe_file(@recipe_file_name)[0].should == @recipe_text
+ end
+ it "should return a file_handle" do
+ @app.read_recipe_file(@recipe_file_name)[1].should be_instance_of(RSpec::Mocks::Mock)
+ end
+ describe "when recipe doesn't exist" do
+ before do
+ File.stub!(:exist?).with(@recipe_file_name).and_return(false)
+ end
+ it "should raise a fatal" do
+ Chef::Application.should_receive(:fatal!)
+ @app.read_recipe_file(@recipe_file_name)
+ end
+ end
+ end
+ describe "temp_recipe_file" do
+ before do
+ @app.instance_variable_set(:@recipe_text, @recipe_text)
+ @app.temp_recipe_file
+ @recipe_fh = @app.instance_variable_get(:@recipe_fh)
+ end
+ it "should open a tempfile" do
+ @recipe_fh.path.should match(/.*recipe-temporary-file.*/)
+ end
+ it "should write recipe text to the tempfile" do
+ @recipe_fh.read.should == @recipe_text
+ end
+ it "should save the filename for later use" do
+ @recipe_fh.path.should == @app.instance_variable_get(:@recipe_filename)
+ end
+ end
+end