diff options
author | Adam Jacob <adam@hjksolutions.com> | 2008-07-10 19:58:43 -0700 |
---|---|---|
committer | Adam Jacob <adam@hjksolutions.com> | 2008-07-10 19:58:43 -0700 |
commit | d734c714c1e7598ddba40b47c26d10f002e06420 (patch) | |
tree | f45f547500cc1bcb10a1fad4bb5af5213736c2e9 | |
parent | c4e77b13c03d4d43b94b051cc819ff83a42f0ab5 (diff) | |
download | chef-d734c714c1e7598ddba40b47c26d10f002e06420.tar.gz |
Adding functional search support
28 files changed, 560 insertions, 91 deletions
@@ -15,8 +15,7 @@ Hoe.new('chef', Chef::VERSION) do |p| p.summary = 'A configuration management system.' p.description = p.paragraphs_of('README.txt', 2..5).join("\n\n") p.url = p.paragraphs_of('README.txt', 0).first.split(/\n/)[1..-1] - p.changes = p.paragraphs_of('History.txt', 0..1).join("\n\n") - p.extra_deps = + p.changes = p.paragraphs_of('History.txt', 0..1).join("\n\n") end # vim: syntax=Ruby diff --git a/bin/chef-indexer b/bin/chef-indexer index 0292002337..5ac363f58b 100755 --- a/bin/chef-indexer +++ b/bin/chef-indexer @@ -62,19 +62,22 @@ end # Get a Chef::SearchIndex object indexer = Chef::SearchIndex.new Chef::Queue.connect -Chef::Queue.subscribe(:queue, "node_index") -Chef::Queue.subscribe(:queue, "node_remove") +Chef::Queue.subscribe(:queue, "index") +Chef::Queue.subscribe(:queue, "remove") while 1 begin object, headers = Chef::Queue.receive_msg - if headers["destination"] =~ /index$/ + Chef::Log.info("Headers #{headers.inspect}") + if headers["destination"] == "/queue/chef/index" start_timer = Time.new indexer.add(object) + indexer.commit final_timer = Time.new Chef::Log.info("Indexed object from #{headers['destination']} in #{final_timer - start_timer} seconds") - elsif headers["destination"] =~ /remove$/ + elsif headers["destination"] == "/queue/chef/remove" start_timer = Time.new indexer.delete(object) + indexer.commit final_timer = Time.new Chef::Log.info("Removed object from #{headers['destination']} in #{final_timer - start_timer} seconds") end @@ -82,6 +85,6 @@ while 1 if e.kind_of?(Interrupt) raise e end - Chef::Log.error("Received Exception: #{e.to_str} continuing") + Chef::Log.error("Received Exception: #{e}\n#{e.backtrace.join("\n")} continuing") end end diff --git a/examples/config/cookbooks/fakefile/recipes/default.rb b/examples/config/cookbooks/fakefile/recipes/default.rb index d02e6286b2..7f44e5686b 100644 --- a/examples/config/cookbooks/fakefile/recipes/default.rb +++ b/examples/config/cookbooks/fakefile/recipes/default.rb @@ -19,12 +19,20 @@ end end end -search(:nodes, "operatingsystem:Darwin") do |server| - hyperic_node "#{server.name}" do - server... - - end +directory "/tmp/home" do + owner "root" + mode 0755 + action :create end -search(:users, "department:hr") do |people| -end +search(:user, "*") do |u| + directory "/tmp/home/#{u[:name]}" do + if u[:name] == "nobody" && @node[:operatingsystem] == "Darwin" + owner "root" + else + owner "#{u[:name]}" + end + mode 0755 + action :create + end +end
\ No newline at end of file diff --git a/examples/search_syntax.rb b/examples/search_syntax.rb new file mode 100644 index 0000000000..4be57b7802 --- /dev/null +++ b/examples/search_syntax.rb @@ -0,0 +1,10 @@ +search(:users, "allowed:#{node[:hostname]} or allowed:#{node[:tags]}") do |u| + user "#{u['username']}" do + uid "#{u['uid']}" + gid "#{u['gid']}" + username "#{u['username']}" + homedir "#{u['homedir']}" + action :create + end +end + diff --git a/examples/user_index.pl b/examples/user_index.pl new file mode 100755 index 0000000000..e78a8125f4 --- /dev/null +++ b/examples/user_index.pl @@ -0,0 +1,115 @@ +#!/usr/bin/perl +# +# M00se on the L00se + +package Chef::Rest; + +use strict; +use warnings; + +use LWP::UserAgent; +use URI; +use Params::Validate qw(:all); +use JSON::Syck; + +sub new { + my $self = shift; + my %p = validate(@_, + { + content_type => { type => SCALAR }, + }, + ); + my $ref = { + 'ua' => LWP::UserAgent->new, + 'content_type' => $p{'content_type'}, + }; + bless $ref, $self; +} + +sub load { + my $self = shift; + my $data = shift; + return JSON::Syck::Load($data); +} + +sub get { + my $self = shift; + my %p = validate(@_, + { + url => { type => SCALAR }, + params => { type => ARRAYREF, optional => 1 }, + }, + ); + + my $url = URI->new($p{'url'}); + if (defined($p{'params'})) { + $url->query_form($p{'params'}); + } + my $req = HTTP::Request->new('GET' => $url); + $req->content_type($self->{'content_type'}); + return $self->ua->request($req); +} + +sub delete { + my $self = shift; + my %p = validate(@_, + { + url => { type => SCALAR }, + }, + ); + my $req = HTTP::Request->new('DELETE' => $p{'url'}); + $req->content_type($self->{'content_type'}); + return $self->ua->request($req); +} + +sub put { + my $self = shift; + my %p = validate(@_, + { + url => { type => SCALAR }, + data => 1, + }, + ); + my $data = JSON::Syck::Dump($p{'data'}); + my $req = HTTP::Request->new('PUT' => $p{'url'}); + $req->content_type($self->{'content_type'}); + $req->content_length(do { use bytes; length($data) }); + $req->content($data); + return $self->ua->request($req); +} + +sub post { + my $self = shift; + my %p = validate(@_, + { + url => { type => SCALAR }, + data => { required => 1 }, + }, + ); + my $data = JSON::Syck::Dump($p{'data'}); + my $req = HTTP::Request->new('POST' => $p{'url'}); + $req->content_type($self->{'content_type'}); + $req->content_length(do { use bytes; length($data) }); + $req->content($data); + return $self->{ua}->request($req); +} + +my $rest = Chef::Rest->new(content_type => 'application/json'); + +while (my @passwd = getpwent) { + print "Ensuring we have $passwd[0]\n"; + $rest->post( + url => 'http://localhost:4000/search/user/entries', + data => { + id => $passwd[0], + name => $passwd[0], + uid => $passwd[2], + gid => $passwd[3], + gecos => $passwd[6], + dir => $passwd[7], + shell => $passwd[8], + change => '', + expire => $passwd[9], + } + ) +} diff --git a/examples/user_index.rb b/examples/user_index.rb new file mode 100755 index 0000000000..485cff81b8 --- /dev/null +++ b/examples/user_index.rb @@ -0,0 +1,27 @@ +#!/usr/bin/env ruby +# +# Create a users index, based on /etc/passwd + +require 'etc' +require File.join(File.dirname(__FILE__), "..", "lib", "chef") + +Chef::Config[:log_level] = :info +r = Chef::REST.new("http://localhost:4000") + +users = Array.new +Etc.passwd do |passwd| + Chef::Log.info("Ensuring we have #{passwd.name}") + r.post_rest("search/user/entries", + { + :id => passwd.name, + :name => passwd.name, + :uid => passwd.uid, + :gid => passwd.gid, + :gecos => passwd.gecos, + :dir => passwd.dir, + :shell => passwd.shell, + :change => passwd.change, + :expire => passwd.expire + } + ) +end diff --git a/lib/chef/exceptions.rb b/lib/chef/exceptions.rb new file mode 100644 index 0000000000..687b962b0e --- /dev/null +++ b/lib/chef/exceptions.rb @@ -0,0 +1,25 @@ +# +# Author:: Adam Jacob (<adam@hjksolutions.com>) +# Copyright:: Copyright (c) 2008 HJK Solutions, LLC +# License:: GNU General Public License version 2 or later +# +# This program and entire repository is free software; you can +# redistribute it and/or modify it under the terms of the GNU +# General Public License as published by the Free Software +# Foundation; either version 2 of the License, or any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +# + +class Chef + class Exception + class SearchIndex < RuntimeError; end + end +end
\ No newline at end of file diff --git a/lib/chef/node.rb b/lib/chef/node.rb index d7350ac2fa..38b8ccc5d4 100644 --- a/lib/chef/node.rb +++ b/lib/chef/node.rb @@ -173,10 +173,21 @@ class Chef end end + def to_index + index_hash = { + :index_name => "node", + :id => "node_#{@name}", + :name => @name, + } + @attribute.each do |key, value| + index_hash[key] = value + end + index_hash[:recipe] = @recipe_list if @recipe_list.length > 0 + index_hash + end + # Serialize this object as a hash def to_json(*a) - attributes = Hash.new - recipes = Array.new result = { "name" => @name, 'json_class' => self.class.name, @@ -220,13 +231,13 @@ class Chef # Remove this node from the CouchDB def destroy - Chef::Queue.send_msg(:queue, :node_remove, self) + Chef::Queue.send_msg(:queue, :remove, self) @couchdb.delete("node", @name, @couchdb_rev) end # Save this node to the CouchDB def save - Chef::Queue.send_msg(:queue, :node_index, self) + Chef::Queue.send_msg(:queue, :index, self) results = @couchdb.store("node", @name, self) @couchdb_rev = results["rev"] end diff --git a/lib/chef/recipe.rb b/lib/chef/recipe.rb index d7d1435eb4..47a194eda7 100644 --- a/lib/chef/recipe.rb +++ b/lib/chef/recipe.rb @@ -70,6 +70,11 @@ class Chef def resources(*args) @collection.resources(*args) end + + def search(type, query, &block) + s = Chef::Search.new + s.search(type, query, &block) + end def method_missing(method_symbol, *args, &block) resource = nil @@ -98,6 +103,7 @@ class Chef end begin args << @collection + args << @node resource = eval(rname).new(*args) resource.params = @params resource.instance_eval(&block) diff --git a/lib/chef/resource.rb b/lib/chef/resource.rb index 74f27bcb82..e42b791b1d 100644 --- a/lib/chef/resource.rb +++ b/lib/chef/resource.rb @@ -29,15 +29,16 @@ class Chef include Chef::Mixin::ParamsValidate attr_accessor :actions, :params, :provider, :updated, :allowed_actions, :collection - attr_reader :resource_name, :source_line + attr_reader :resource_name, :source_line, :node - def initialize(name, collection=nil) + def initialize(name, collection=nil, node=nil) @name = name if collection @collection = collection else @collection = Chef::ResourceCollection.new() end + @node = node ? node : Chef::Node.new @noop = nil @before = nil @actions = Hash.new diff --git a/lib/chef/resource/directory.rb b/lib/chef/resource/directory.rb index 24e683bef9..1e13891fae 100644 --- a/lib/chef/resource/directory.rb +++ b/lib/chef/resource/directory.rb @@ -22,9 +22,9 @@ class Chef class Resource class Directory < Chef::Resource - def initialize(name, collection=nil) + def initialize(name, collection=nil, node=nil) @resource_name = :directory - super(name, collection) + super(name, collection, node) @path = name @action = :create @allowed_actions.push(:create, :delete) diff --git a/lib/chef/resource/file.rb b/lib/chef/resource/file.rb index ce8b2f35f9..f1cfa86f9a 100644 --- a/lib/chef/resource/file.rb +++ b/lib/chef/resource/file.rb @@ -23,9 +23,9 @@ class Chef class Resource class File < Chef::Resource - def initialize(name, collection=nil) + def initialize(name, collection=nil, node=nil) @resource_name = :file - super(name, collection) + super(name, collection, node) @path = name @backup = true @action = "create" diff --git a/lib/chef/resource/link.rb b/lib/chef/resource/link.rb index 743ef5ea73..5ec3b39b25 100644 --- a/lib/chef/resource/link.rb +++ b/lib/chef/resource/link.rb @@ -22,9 +22,9 @@ class Chef class Resource class Link < Chef::Resource - def initialize(name, collection=nil) + def initialize(name, collection=nil, node=nil) @resource_name = :link - super(name, collection) + super(name, collection, node) @source_file = name @action = :create @link_type = :symbolic diff --git a/lib/chef/rest.rb b/lib/chef/rest.rb index f71c203b3a..e4474fb9eb 100644 --- a/lib/chef/rest.rb +++ b/lib/chef/rest.rb @@ -106,6 +106,7 @@ class Chef else raise ArgumentError, "You must provide :GET, :PUT, :POST or :DELETE as the method" end + Chef::Log.debug("Sending HTTP Request via #{req.method} to #{req.path}") res = http.request(req) if res.kind_of?(Net::HTTPSuccess) if res['set-cookie'] diff --git a/lib/chef/search.rb b/lib/chef/search.rb index fe7e774e1b..dba38f79e9 100644 --- a/lib/chef/search.rb +++ b/lib/chef/search.rb @@ -24,6 +24,8 @@ require 'ferret' class Chef class Search + attr_reader :index + def initialize @index = Ferret::Index::Index.new(:path => Chef::Config[:search_index_path]) end @@ -31,18 +33,44 @@ class Chef def search(type, query, &block) search_query = build_search_query(type, query) start_time = Time.now + result = Array.new + if Kernel.block_given? - @index.search_each(search_query, &block) + result = @index.search_each(search_query, :limit => :all) do |id, score| + block.call(build_hash(@index.doc(id))) + end else - @index.search(search_query) + @index.search_each(search_query, :limit => :all) do |id, score| + result << build_hash(@index.doc(id)) + end end Chef::Log.debug("Search #{search_query} complete in #{Time.now - start_time} seconds") + result + end + + def list_indexes + indexes = Hash.new + @index.search_each("index_name:*", :limit => :all) do |id, score| + indexes[@index.doc(id)["index_name"]] = true + end + indexes.keys + end + + def has_index?(index) + list_indexes.detect { |i| i == index } end private def build_search_query(type, query) - "type:#{type} AND (#{query})" + "index_name:#{type} AND (#{query})" end + def build_hash(doc) + result = Hash.new + doc.fields.each do |f| + result[f] = doc[f] + end + result + end end end
\ No newline at end of file diff --git a/lib/chef/search_index.rb b/lib/chef/search_index.rb index 6af408b68c..d4d4f77042 100644 --- a/lib/chef/search_index.rb +++ b/lib/chef/search_index.rb @@ -24,6 +24,8 @@ require 'ferret' class Chef class SearchIndex + attr_reader :index + def initialize @index = Ferret::Index::Index.new( :path => Chef::Config[:search_index_path], @@ -31,40 +33,50 @@ class Chef ) end - def add(to_index) - type = check_type(to_index) - result = self.send("_prepare_#{type}", to_index) - Chef::Log.debug("Indexing #{type} with #{result.inspect}") - @index.add_document(result) + def add(new_object) + index_hash = create_index_object(new_object) + Chef::Log.debug("Indexing #{index_hash[:index_name]} with #{index_hash.inspect}") + @index.add_document(index_hash) end + def create_index_object(new_object) + index_hash = nil + + if new_object.respond_to?(:to_index) + index_hash = new_object.to_index + elsif new_object.kind_of?(Hash) + index_hash = new_object + else + raise Chef::Exception::SearchIndex, "Cannot transform argument to a Hash!" + end + + unless index_hash.has_key?(:index_name) || index_hash.has_key?("index_name") + raise Chef::Exception::SearchIndex, "Cannot index without an index_name key: #{index_hash.inspect}" + end + + unless index_hash.has_key?(:id) || index_hash.has_key?("id") + raise Chef::Exception::SearchIndex, "Cannot index without an id key: #{index_hash.inspect}" + end + + index_hash.each do |k,v| + unless k.kind_of?(Symbol) + index_hash[k.to_sym] = v + index_hash.delete(k) + end + end + + index_hash + end + def delete(index_obj) - type = check_type(index_obj) - to_index = self.send("_prepare_#{type}", index_obj) - Chef::Log.debug("Removing #{type} with #{to_index.inspect}") - @index.delete(:id => "#{to_index[:id]}") + to_delete = create_index_object(index_obj) + Chef::Log.debug("Removing #{to_delete.inspect} from the #{to_delete[:index_name]} index") + @index.delete(to_delete[:id]) end - private - - def check_type(to_check) - type = nil - case to_check - when Chef::Node - type = "node" - end - end + def commit + @index.commit + end - def _prepare_node(node) - result = Hash.new - result[:id] = "node-#{node.name}" - result[:type] = "node" - result[:name] = node.name - node.each_attribute do |k,v| - result[k.to_sym] = v - end - result[:recipe] = node.recipes - result - end end -end
\ No newline at end of file +end diff --git a/lib/chef_server/controllers/search.rb b/lib/chef_server/controllers/search.rb new file mode 100644 index 0000000000..17c0d553a4 --- /dev/null +++ b/lib/chef_server/controllers/search.rb @@ -0,0 +1,53 @@ +class Search < Application + + provides :html, :json + + def index + @s = Chef::Search.new + @search_indexes = @s.list_indexes + display @search_indexes + end + + def show + @s = Chef::Search.new + @results = nil + if params[:q] + @results = @s.search(params[:id], params[:q] == "" ? "?*" : params[:q]) + else + @results = @s.search(params[:id], "?*") + end + # Boy, this should move to the search function + if params[:a] + attributes = params[:a].split(",").collect { |a| a.to_sym } + unless attributes.length == 0 + @results = @results.collect do |r| + nr = Hash.new + nr[:index_name] = r[:index_name] + nr[:id] = r[:id] + attributes.each do |attrib| + if r.has_key?(attrib) + nr[attrib] = r[attrib] + end + end + nr + end + end + end + display @results + end + + def destroy + @s = Chef::Search.new + @entries = @s.search(params[:id], "?*") + @entries.each do |entry| + Chef::Queue.send_msg(:queue, :remove, entry) + end + @status = 202 + if content_type == :html + redirect url(:search) + else + display @entries + end + end + +end diff --git a/lib/chef_server/controllers/search_entries.rb b/lib/chef_server/controllers/search_entries.rb new file mode 100644 index 0000000000..ab79b57b3c --- /dev/null +++ b/lib/chef_server/controllers/search_entries.rb @@ -0,0 +1,50 @@ +class SearchEntries < Application + + provides :html, :json + + def index + @s = Chef::Search.new + @entries = @s.search(params[:search_id], "?*") + display @entries + end + + def show + @s = Chef::Search.new + @entry = @s.search(params[:search_id], "id:'#{params[:search_id]}_#{params[:id]}'").first + display @entry + end + + def create + @to_index = params + @to_index.delete(:controller) + @to_index["index_name"] = params[:search_id] + @to_index["id"] = "#{params[:search_id]}_#{params[:id]}" + @to_index.delete(:search_id) + Chef::Queue.send_msg(:queue, :index, @to_index) + if content_type == :html + redirect url(:search) + else + @status = 202 + display @to_index + end + end + + def update + create + end + + def destroy + @s = Chef::Search.new + @entries = @s.search(params[:id], "?*") + @entries.each do |entry| + Chef::Queue.send_msg(:queue, :remove, entry) + end + @status = 202 + if content_type == :html + redirect url(:search) + else + display @entries + end + end + +end diff --git a/lib/chef_server/init.rb b/lib/chef_server/init.rb index 136ad55e26..ca1692b6cf 100644 --- a/lib/chef_server/init.rb +++ b/lib/chef_server/init.rb @@ -135,6 +135,9 @@ Merb::Router.prepare do |r| r.resources :nodes r.resources :nodes, :member => { :compile => :get } + r.resources :search do |res| + res.resources :entries, :controller => "search_entries" + end #r.resources :openid do |res| # res.resources :register, :controller => "openid_register" diff --git a/lib/chef_server/views/layout/application.html.haml b/lib/chef_server/views/layout/application.html.haml index f04f299d2c..4e4d235e78 100644 --- a/lib/chef_server/views/layout/application.html.haml +++ b/lib/chef_server/views/layout/application.html.haml @@ -7,6 +7,8 @@ %link{:rel => "stylesheet", :href => "/stylesheets/master.css", :type => "text/css", :media => "screen", :charset => "utf-8" } %body .header + %a{:href => url(:search) } Search + | %a{:href => url(:nodes) } Nodes | %a{:href => url(:registrations)} Registrations diff --git a/lib/chef_server/views/search/_search_form.html.haml b/lib/chef_server/views/search/_search_form.html.haml new file mode 100644 index 0000000000..99303e1714 --- /dev/null +++ b/lib/chef_server/views/search/_search_form.html.haml @@ -0,0 +1,6 @@ +%form{ :method => "get", :action => url(:search, { :id => index_name }) } + Q: + %input{ :type => "text", :name => "q" } + A: + %input{ :type => "text", :name => "a" } + %input{ :type => "submit", :value => "Search #{index_name}" } diff --git a/lib/chef_server/views/search/index.html.haml b/lib/chef_server/views/search/index.html.haml new file mode 100644 index 0000000000..742c1110b0 --- /dev/null +++ b/lib/chef_server/views/search/index.html.haml @@ -0,0 +1,9 @@ +%h1 Search Indexes +- @search_indexes.each do |index| + .index + %table + %tr + %td + %a{ :href => url(:search, { :id => index }) }= index + %td + = partial(:search_form, :index_name => index) diff --git a/lib/chef_server/views/search/show.html.haml b/lib/chef_server/views/search/show.html.haml new file mode 100644 index 0000000000..5380a7477a --- /dev/null +++ b/lib/chef_server/views/search/show.html.haml @@ -0,0 +1,13 @@ +%h1 Search Results +.search + = partial(:search_form, :index_name => params[:id]) +.query + %h2= "Search Query was #{params[:q] ? params[:q] : '*'}" +- @results.each do |result| + .search_result + %h3= "#{h result[:index_name]} (#{h result[:id]})" + %table + - result.each do |k, v| + %tr.attr_group + %td.attr_name= k + %td.attr_value= v.kind_of?(Array) ? v.join(",") : v
\ No newline at end of file diff --git a/lib/chef_server/views/search_entries/index.html.haml b/lib/chef_server/views/search_entries/index.html.haml new file mode 100644 index 0000000000..77d140242e --- /dev/null +++ b/lib/chef_server/views/search_entries/index.html.haml @@ -0,0 +1,9 @@ +%h1= "Search Index #{h params[:search_id]}" +- @entries.each do |result| + .search_result + %h3= "#{h result[:index_name]} (#{h result[:id]})" + %table + - result.each do |k, v| + %tr.attr_group + %td.attr_name= k + %td.attr_value= v.kind_of?(Array) ? v.join(",") : v
\ No newline at end of file diff --git a/lib/chef_server/views/search_entries/show.html.haml b/lib/chef_server/views/search_entries/show.html.haml new file mode 100644 index 0000000000..7ca34b32ab --- /dev/null +++ b/lib/chef_server/views/search_entries/show.html.haml @@ -0,0 +1,8 @@ +%h1= "Search Index #{h params[:search_id]} entry #{h params[:id]}" +.search_result + %h3= "#{h @entry[:index_name]} (#{h @entry[:id]})" + %table + - @entry.each do |k, v| + %tr.attr_group + %td.attr_name= k + %td.attr_value= v.kind_of?(Array) ? v.join(",") : v
\ No newline at end of file diff --git a/spec/unit/node_spec.rb b/spec/unit/node_spec.rb index e41c2235a3..81fbeb281b 100644 --- a/spec/unit/node_spec.rb +++ b/spec/unit/node_spec.rb @@ -218,6 +218,18 @@ describe Chef::Node, "json" do end end +describe Chef::Node, "to_index" do + before(:each) do + Chef::Config.node_path(File.join(File.dirname(__FILE__), "..", "data", "nodes")) + @node = Chef::Node.new() + end + + it "should return a hash with :index attributes" do + @node.name("airplane") + @node.to_index.should == { :index_name => "node", :id => "node_airplane", :name => "airplane" } + end +end + describe Chef::Node, "to_s" do before(:each) do @@ -277,7 +289,7 @@ describe Chef::Node, "destroy" do node = Chef::Node.new node.name "bob" node.couchdb_rev = 1 - Chef::Queue.should_receive(:send_msg).with(:queue, :node_remove, node) + Chef::Queue.should_receive(:send_msg).with(:queue, :remove, node) node.destroy end end @@ -294,8 +306,8 @@ describe Chef::Node, "save" do end it "should save the node to couchdb" do - Chef::Queue.should_receive(:send_msg).with(:queue, :node_index, @node) - @mock_couch.should_receive(:store).with("node", "bob", @node).and_return({ "rev" => 33 }) + Chef::Queue.should_receive(:send_msg).with(:queue, :index, @node) + @mock_couch.should_receive(:store).with("node", "bob", @node).and_return({ "rev" => 33 }) @node.save end diff --git a/spec/unit/search.rb b/spec/unit/search.rb index 52ec6c3439..702997a565 100644 --- a/spec/unit/search.rb +++ b/spec/unit/search.rb @@ -60,17 +60,17 @@ describe Chef::Search, "search method" do it "should call search_each if a block is given" do cp = lambda { |n| "noting to do here" } - @mf.should_receive(:search_each).with("type:node AND (tag:monkey)", &cp) + @mf.should_receive(:search_each).with("index_name:node AND (tag:monkey)", &cp) do_search(:node, "tag:monkey", &cp) end it "should call search if a block is not given" do - @mf.should_receive(:search).with("type:node AND (tag:monkey)") + @mf.should_receive(:search).with("index_name:node AND (tag:monkey)") do_search(:node, "tag:monkey") end it "should return the search results" do - @mf.should_receive(:search).with("type:node AND (tag:monkey)").and_return(true) + @mf.should_receive(:search).with("index_name:node AND (tag:monkey)").and_return(true) do_search(:node, "tag:monkey").should eql(true) end end
\ No newline at end of file diff --git a/spec/unit/search_index_spec.rb b/spec/unit/search_index_spec.rb index 2957f9c774..93b16364e5 100644 --- a/spec/unit/search_index_spec.rb +++ b/spec/unit/search_index_spec.rb @@ -20,38 +20,96 @@ require File.expand_path(File.join(File.dirname(__FILE__), "..", "spec_helper")) -describe Chef::SearchIndex do +describe Chef::SearchIndex, "initialize method" do + it "should create a new Chef::SearchIndex object" do + mf = mock("Ferret::Index::Index", :null_object => true) + Ferret::Index::Index.stub!(:new).and_return(mf) + Chef::SearchIndex.new.should be_kind_of(Chef::SearchIndex) + end + + it "should create a Ferret Indexer" do + mf = mock("Ferret::Index::Index", :null_object => true) + Ferret::Index::Index.should_receive(:new).and_return(mf) + Chef::SearchIndex.new + end +end + +describe Chef::SearchIndex, "create_index_object method" do before(:each) do - @fake_indexer = stub("Indexer", :null_object => true) - Ferret::Index::Index.stub!(:new).and_return(@fake_indexer) - @sindex = Chef::SearchIndex.new() - @node = Chef::Node.new - @node.name "adam.foo.com" - @node.fqdn "adam.foo.com" - @node.mars "volta" - @node.recipes "one", "two" + @mf = mock("Ferret::Index::Index", :null_object => true) + @fakeobj = mock("ToIndex", :null_object => true) + @the_pigeon = { :index_name => "bird", :id => "pigeon" } + @fakeobj.stub!(:responds_to?).with(:to_index).and_return(true) + @fakeobj.stub!(:to_index).and_return(@the_pigeon) + Ferret::Index::Index.stub!(:new).and_return(@mf) + end + + def do_create_index_object + index = Chef::SearchIndex.new + index.create_index_object(@fakeobj) end + + it "should call to_index if the passed object responds to it" do + @fakeobj.should_receive(:responds_to?).with(:to_index).and_return(true) + @fakeobj.should_receive(:to_index).and_return(@the_pigeon) + do_create_index_object + end + + it "should use a hash if the passed argument does not have to_index (but is a hash)" do + @fakeobj.stub!(:responds_to?).with(:to_index).and_return(false) + @fakeobj.should_receive(:kind_of?).with(Hash).and_return(true) + do_create_index_object + end + + it "should raise SearchIndex exception if the hash does not contain an :id field" do + @the_pigeon.delete(:id) + lambda { do_create_index_object }.should raise_error(Chef::Exception::SearchIndex) + end + + it "should raise SearchIndex exception if the hash does not contain an :index_name field" do + @the_pigeon.delete(:index_name) + lambda { do_create_index_object }.should raise_error(Chef::Exception::SearchIndex) + end +end - it "should index a node object with add" do - @sindex.should_receive(:_prepare_node).with(@node).and_return("my value") - @fake_indexer.should_receive(:add_document).with("my value") - @sindex.add(@node) +describe Chef::SearchIndex, "add method" do + before(:each) do + @mf = mock("Ferret::Index::Index", :null_object => true) + @fakeobj = mock("ToIndex", :null_object => true) + @the_pigeon = { :index_name => "bird", :id => "pigeon" } + @fakeobj.stub!(:responds_to?).with(:to_index).and_return(true) + @fakeobj.stub!(:to_index).and_return(@the_pigeon) + Ferret::Index::Index.stub!(:new).and_return(@mf) end - it "should remove a node from the index with delete" do - @sindex.should_receive(:_prepare_node).with(@node).and_return({ :id => "node-my value" }) - @fake_indexer.should_receive(:delete).with(:id => "node-my value") - @sindex.delete(@node) + def do_add + index = Chef::SearchIndex.new + index.add(@fakeobj) + end + + it "should send the resulting hash to the index" do + @mf.should_receive(:add_document).with(@the_pigeon) + do_add + end +end + +describe Chef::SearchIndex, "delete method" do + before(:each) do + @mf = mock("Ferret::Index::Index", :null_object => true) + @fakeobj = mock("ToIndex", :null_object => true) + @the_pigeon = { :index_name => "bird", :id => "pigeon" } + @fakeobj.stub!(:responds_to?).with(:to_index).and_return(true) + @fakeobj.stub!(:to_index).and_return(@the_pigeon) + Ferret::Index::Index.stub!(:new).and_return(@mf) end - it "should prepare a node by creating a proper hash" do - node_hash = @sindex.send(:_prepare_node, @node) - node_hash[:id].should eql("node-adam.foo.com") - node_hash[:type].should eql("node") - node_hash[:name].should eql("adam.foo.com") - node_hash[:fqdn].should eql("adam.foo.com") - node_hash[:mars].should eql("volta") - node_hash[:recipe].should eql(["one", "two"]) + def do_delete(object) + index = Chef::SearchIndex.new + index.delete(object) end -end
\ No newline at end of file + it "should delete the resulting hash to the index" do + @mf.should_receive(:delete).with({ :id => @the_pigeon[:id] }) + do_delete(@fakeobj) + end +end |