diff options
author | Dan DeLeo <danielsdeleo@mac.com> | 2009-11-09 21:00:42 -0700 |
---|---|---|
committer | Dan DeLeo <danielsdeleo@mac.com> | 2009-11-09 21:03:14 -0700 |
commit | cae17ff59ee1ccfabbb338de39e6ccac13693f15 (patch) | |
tree | d1ccd4dec0c32f3caf5e50786906f8c1be869a2f | |
parent | e900e68f04d7b4d0551322796eecd3041b28a2a2 (diff) | |
download | chef-cae17ff59ee1ccfabbb338de39e6ccac13693f15.tar.gz |
[CHEF-687] initial shef import
-rwxr-xr-x | chef/bin/shef | 24 | ||||
-rw-r--r-- | chef/lib/chef/shef.rb | 228 | ||||
-rw-r--r-- | chef/lib/chef/shef/ext.rb | 135 | ||||
-rw-r--r-- | chef/lib/chef/shef/init.rb | 38 | ||||
-rw-r--r-- | chef/spec/spec_helper.rb | 6 | ||||
-rw-r--r-- | chef/spec/unit/cookbook/metadata_spec.rb | 4 | ||||
-rw-r--r-- | chef/spec/unit/recipe_spec.rb | 1 | ||||
-rw-r--r-- | chef/spec/unit/shef_spec.rb | 245 |
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 |