summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAdam Jacob <adam@hjksolutions.com>2008-07-10 19:58:43 -0700
committerAdam Jacob <adam@hjksolutions.com>2008-07-10 19:58:43 -0700
commitd734c714c1e7598ddba40b47c26d10f002e06420 (patch)
treef45f547500cc1bcb10a1fad4bb5af5213736c2e9
parentc4e77b13c03d4d43b94b051cc819ff83a42f0ab5 (diff)
downloadchef-d734c714c1e7598ddba40b47c26d10f002e06420.tar.gz
Adding functional search support
-rw-r--r--Rakefile3
-rwxr-xr-xbin/chef-indexer13
-rw-r--r--examples/config/cookbooks/fakefile/recipes/default.rb22
-rw-r--r--examples/search_syntax.rb10
-rwxr-xr-xexamples/user_index.pl115
-rwxr-xr-xexamples/user_index.rb27
-rw-r--r--lib/chef/exceptions.rb25
-rw-r--r--lib/chef/node.rb19
-rw-r--r--lib/chef/recipe.rb6
-rw-r--r--lib/chef/resource.rb5
-rw-r--r--lib/chef/resource/directory.rb4
-rw-r--r--lib/chef/resource/file.rb4
-rw-r--r--lib/chef/resource/link.rb4
-rw-r--r--lib/chef/rest.rb1
-rw-r--r--lib/chef/search.rb34
-rw-r--r--lib/chef/search_index.rb72
-rw-r--r--lib/chef_server/controllers/search.rb53
-rw-r--r--lib/chef_server/controllers/search_entries.rb50
-rw-r--r--lib/chef_server/init.rb3
-rw-r--r--lib/chef_server/views/layout/application.html.haml2
-rw-r--r--lib/chef_server/views/search/_search_form.html.haml6
-rw-r--r--lib/chef_server/views/search/index.html.haml9
-rw-r--r--lib/chef_server/views/search/show.html.haml13
-rw-r--r--lib/chef_server/views/search_entries/index.html.haml9
-rw-r--r--lib/chef_server/views/search_entries/show.html.haml8
-rw-r--r--spec/unit/node_spec.rb18
-rw-r--r--spec/unit/search.rb6
-rw-r--r--spec/unit/search_index_spec.rb110
28 files changed, 560 insertions, 91 deletions
diff --git a/Rakefile b/Rakefile
index 97b1dc76c9..eebcdedd69 100644
--- a/Rakefile
+++ b/Rakefile
@@ -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