summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorScott Hain <shain@getchef.com>2014-06-25 13:40:08 -0700
committerScott Hain <shain@getchef.com>2014-06-25 13:40:08 -0700
commitffe8726ffb606e3ed291587b09c4a78e9bde59b3 (patch)
tree4945ece2bb2f9080eb9ad774009bb4791138f71d
parent6b463773ffc91056146a59c7e69f14e563821604 (diff)
downloadchef-ffe8726ffb606e3ed291587b09c4a78e9bde59b3.tar.gz
Finished up search with filtering
-rw-r--r--lib/chef/knife/search.rb36
-rw-r--r--lib/chef/search/query.rb73
-rw-r--r--spec/unit/search/query_spec.rb269
3 files changed, 348 insertions, 30 deletions
diff --git a/lib/chef/knife/search.rb b/lib/chef/knife/search.rb
index 49c3be3c62..1bb182a61b 100644
--- a/lib/chef/knife/search.rb
+++ b/lib/chef/knife/search.rb
@@ -25,7 +25,6 @@ class Chef
include Knife::Core::MultiAttributeReturnOption
- puts "GJGKDLGJKDLSJFKDLSFDJSFDS"
deps do
require 'chef/node'
require 'chef/environment'
@@ -72,6 +71,11 @@ class Chef
:long => "--query QUERY",
:description => "The search query; useful to protect queries starting with -"
+ option :filter_result,
+ :short => "-f FILTER",
+ :long => "--filter-result FILTER",
+ :description => "Only bring back specific elements of the matching objects"
+
def run
read_cli_args
fuzzify_query
@@ -88,10 +92,19 @@ class Chef
result_items = []
result_count = 0
- rows = config[:rows]
- start = config[:start]
+ # rows = config[:rows]
+ # start = config[:start]
+ search_args = Hash.new
+ search_args[:sort] = config[:sort]
+ search_args[:start] = config[:start]
+ search_args[:rows] = config[:rows]
+ if config[:filter_result]
+ search_args[:filter_result] = create_result_filter(config[:filter_result])
+ end
+
begin
- q.search(@type, escaped_query, config[:sort], start, rows) do |item|
+ # TODO - fix formatting for filtered results
+ q.search(@type, escaped_query, search_args) do |item|
formatted_item = format_for_display(item)
# if formatted_item.respond_to?(:has_key?) && !formatted_item.has_key?('id')
# formatted_item['id'] = item.has_key?('id') ? item['id'] : item.name
@@ -150,6 +163,21 @@ class Chef
end
end
+ def create_result_filter(filter_string)
+ final_filter = Hash.new
+
+ # this probably needs review. I betcha there's a better way!!!
+ filter_string.gsub!(" ", "")
+ filters = filter_string.split(",")
+ filters.each do |f|
+ fsplit = f.split("=")
+ return_id = fsplit[0]
+ attr_path = fsplit[1].split(".")
+ final_filter[return_id.to_sym] = attr_path
+ end
+ return final_filter
+ end
+
end
end
end
diff --git a/lib/chef/search/query.rb b/lib/chef/search/query.rb
index 4869ec1484..bf3518e695 100644
--- a/lib/chef/search/query.rb
+++ b/lib/chef/search/query.rb
@@ -34,22 +34,62 @@ class Chef
@rest = Chef::REST.new(url ||Chef::Config[:chef_server_url])
end
- # Search Solr for objects of a given type, for a given query. If you give
- # it a block, it will handle the paging for you dynamically.
- def search(type, query="*:*", sort='X_CHEF_id_CHEF_X asc', start=0, rows=1000, &block)
+ # new search api that allows for a cleaner implementation of things like return filters
+ # (formerly known as 'partial search'). A passthrough to either the old style ("full search")
+ # or the new 'filtered' search
+ def search_new(type, query="*:*", args=nil, &block)
raise ArgumentError, "Type must be a string or a symbol!" unless (type.kind_of?(String) || type.kind_of?(Symbol))
- response = @rest.get_rest("search/#{type}?q=#{escape(query)}&sort=#{escape(sort)}&start=#{escape(start)}&rows=#{escape(rows)}")
+ # if args is nil, we need to set some defaults, for backwards compatibility
+ if args.nil?
+ args = Hash.new
+ args[:sort] = 'X_CHEF_id_CHEF_X asc'
+ args[:start] = 0
+ args[:rows] = 1000
+ end
+
+ query_string = create_query_string(type, query, args)
+ response = call_rest_service(query_string, args)
if block
response["rows"].each { |o| block.call(o) unless o.nil?}
unless (response["start"] + response["rows"].length) >= response["total"]
- nstart = response["start"] + rows
- search(type, query, sort, nstart, rows, &block)
+ args[:start] = response["start"] + args[:rows]
+ search_new(type, query, args, &block)
end
true
else
[ response["rows"], response["start"], response["total"] ]
end
+
+ end
+
+ def search(type, query='*:*', *args, &block)
+ raise ArgumentError, "Type must be a string or a symbol!" unless (type.kind_of?(String) || type.kind_of?(Symbol))
+ raise ArgumentError, "Invalid number of arguments!" if (args.size > 3)
+ if args.size == 1 && args[0].is_a?(Hash)
+ args_hash = args[0]
+ search_new(type, query, args_hash, &block)
+ else
+ sort = args.length >= 1 ? args[0] : 'X_CHEF_id_CHEF_X asc'
+ start = args.length >= 2 ? args[1] : 0
+ rows = args.length >= 3 ? args[2] : 1000
+ search_old(type, query, sort, start, rows, &block)
+ end
+ end
+
+
+ # Search Solr for objects of a given type, for a given query. If you give
+ # it a block, it will handle the paging for you dynamically.
+ def search_old(type, query="*:*", sort='X_CHEF_id_CHEF_X asc', start=0, rows=1000, &block)
+ raise ArgumentError, "Type must be a string or a symbol!" unless (type.kind_of?(String) || type.kind_of?(Symbol))
+
+ # argify things
+ args = Hash.new
+ args[:sort] = sort
+ args[:start] = start
+ args[:rows] = rows
+
+ search_new(type, query, args, &block)
end
def list_indexes
@@ -60,6 +100,27 @@ class Chef
def escape(s)
s && URI.escape(s.to_s)
end
+
+ # create the full rest url string
+ def create_query_string(type, query, args)
+ # create some default variables just so we don't break backwards compatibility
+ sort = args.key?(:sort) ? args[:sort] : 'X_CHEF_id_CHEF_X asc'
+ start = args.key?(:start) ? args[:start] : 0
+ rows = args.key?(:rows) ? args[:rows] : 1000
+
+ return "search/#{type}?q=#{escape(query)}&sort=#{escape(sort)}&start=#{escape(start)}&rows=#{escape(rows)}"
+ end
+
+ def call_rest_service(query_string, args)
+ if args.key?(:filter_result)
+ response = @rest.post_rest(query_string, args[:filter_result])
+ response_rows = response['rows'].map { |row| row['data'] }
+ else
+ response = @rest.get_rest(query_string)
+ response_rows = response['rows']
+ end
+ return response
+ end
end
end
end
diff --git a/spec/unit/search/query_spec.rb b/spec/unit/search/query_spec.rb
index 7463e3bb3c..714806ce29 100644
--- a/spec/unit/search/query_spec.rb
+++ b/spec/unit/search/query_spec.rb
@@ -26,14 +26,62 @@ describe Chef::Search::Query do
@query = Chef::Search::Query.new
end
- describe "search" do
+ describe "legacy search" do
before(:each) do
@response = {
"rows" => [
- { "id" => "for you" },
- { "id" => "hip hop" },
- { "id" => "thought was down by law for you" },
- { "id" => "kept it hard core for you" },
+ { "name" => "my-name-is-node",
+ "chef_environment" => "elysium",
+ "platform" => "rhel",
+ "automatic" => {
+ "languages" => {
+ "ruby" => {
+ "platform" => "nudibranch",
+ "version" => "1.9.3",
+ "target" => "ming-the-merciless"
+ }
+ }
+ }
+ },
+ { "name" => "my-name-is-jonas",
+ "chef_environment" => "hades",
+ "platform" => "rhel",
+ "automatic" => {
+ "languages" => {
+ "ruby" => {
+ "platform" => "i386-mingw32",
+ "version" => "1.9.3",
+ "target" => "bilbo"
+ }
+ }
+ }
+ },
+ { "name" => "my-name-is-flipper",
+ "chef_environment" => "elysium",
+ "platform" => "rhel",
+ "automatic" => {
+ "languages" => {
+ "ruby" => {
+ "platform" => "centos",
+ "version" => "2.0.0",
+ "target" => "uno"
+ }
+ }
+ }
+ },
+ { "name" => "my-name-is-butters",
+ "chef_environment" => "moon",
+ "platform" => "rhel",
+ "automatic" => {
+ "languages" => {
+ "ruby" => {
+ "platform" => "solaris2",
+ "version" => "2.1.2",
+ "target" => "random"
+ }
+ }
+ }
+ },
],
"start" => 0,
"total" => 4
@@ -42,43 +90,43 @@ describe Chef::Search::Query do
end
it "should accept a type as the first argument" do
- lambda { @query.search("foo") }.should_not raise_error
- lambda { @query.search(:foo) }.should_not raise_error
+ lambda { @query.search("node") }.should_not raise_error
+ lambda { @query.search(:node) }.should_not raise_error
lambda { @query.search(Hash.new) }.should raise_error(ArgumentError)
end
it "should query for every object of a type by default" do
- @rest.should_receive(:get_rest).with("search/foo?q=*:*&sort=X_CHEF_id_CHEF_X%20asc&start=0&rows=1000").and_return(@response)
+ @rest.should_receive(:get_rest).with("search/node?q=*:*&sort=X_CHEF_id_CHEF_X%20asc&start=0&rows=1000").and_return(@response)
@query = Chef::Search::Query.new
- @query.search(:foo)
+ @query.search(:node)
end
it "should allow a custom query" do
- @rest.should_receive(:get_rest).with("search/foo?q=gorilla:dundee&sort=X_CHEF_id_CHEF_X%20asc&start=0&rows=1000").and_return(@response)
+ @rest.should_receive(:get_rest).with("search/node?q=platform:rhel&sort=X_CHEF_id_CHEF_X%20asc&start=0&rows=1000").and_return(@response)
@query = Chef::Search::Query.new
- @query.search(:foo, "gorilla:dundee")
+ @query.search(:node, "platform:rhel")
end
it "should let you set a sort order" do
- @rest.should_receive(:get_rest).with("search/foo?q=gorilla:dundee&sort=id%20desc&start=0&rows=1000").and_return(@response)
+ @rest.should_receive(:get_rest).with("search/node?q=platform:rhel&sort=id%20desc&start=0&rows=1000").and_return(@response)
@query = Chef::Search::Query.new
- @query.search(:foo, "gorilla:dundee", "id desc")
+ @query.search(:node, "platform:rhel", "id desc")
end
it "should let you set a starting object" do
- @rest.should_receive(:get_rest).with("search/foo?q=gorilla:dundee&sort=id%20desc&start=2&rows=1000").and_return(@response)
+ @rest.should_receive(:get_rest).with("search/node?q=platform:rhel&sort=id%20desc&start=2&rows=1000").and_return(@response)
@query = Chef::Search::Query.new
- @query.search(:foo, "gorilla:dundee", "id desc", 2)
+ @query.search(:node, "platform:rhel", "id desc", 2)
end
it "should let you set how many rows to return" do
- @rest.should_receive(:get_rest).with("search/foo?q=gorilla:dundee&sort=id%20desc&start=2&rows=40").and_return(@response)
+ @rest.should_receive(:get_rest).with("search/node?q=platform:rhel&sort=id%20desc&start=2&rows=40").and_return(@response)
@query = Chef::Search::Query.new
- @query.search(:foo, "gorilla:dundee", "id desc", 2, 40)
+ @query.search(:node, "platform:rhel", "id desc", 2, 40)
end
it "should return the raw rows, start, and total if no block is passed" do
- rows, start, total = @query.search(:foo)
+ rows, start, total = @query.search(:node)
rows.should equal(@response["rows"])
start.should equal(@response["start"])
total.should equal(@response["total"])
@@ -87,13 +135,194 @@ describe Chef::Search::Query do
it "should call a block for each object in the response" do
@call_me = double("blocky")
@response["rows"].each { |r| @call_me.should_receive(:do).with(r) }
- @query.search(:foo) { |r| @call_me.do(r) }
+ @query.search(:node) { |r| @call_me.do(r) }
end
it "should page through the responses" do
@call_me = double("blocky")
@response["rows"].each { |r| @call_me.should_receive(:do).with(r) }
- @query.search(:foo, "*:*", nil, 0, 1) { |r| @call_me.do(r) }
+ @query.search(:node, "*:*", nil, 0, 1) { |r| @call_me.do(r) }
+ end
+ end
+
+ # copypasta existing functionality for new searhc, because new search should at the very least do the same stuff!
+ describe "new search" do
+ before(:each) do
+ @response = {
+ "rows" => [
+ { "name" => "my-name-is-node",
+ "chef_environment" => "elysium",
+ "platform" => "rhel",
+ "automatic" => {
+ "languages" => {
+ "ruby" => {
+ "platform" => "nudibranch",
+ "version" => "1.9.3",
+ "target" => "ming-the-merciless"
+ }
+ }
+ }
+ },
+ { "name" => "my-name-is-jonas",
+ "chef_environment" => "hades",
+ "platform" => "rhel",
+ "automatic" => {
+ "languages" => {
+ "ruby" => {
+ "platform" => "i386-mingw32",
+ "version" => "1.9.3",
+ "target" => "bilbo"
+ }
+ }
+ }
+ },
+ { "name" => "my-name-is-flipper",
+ "chef_environment" => "elysium",
+ "platform" => "rhel",
+ "automatic" => {
+ "languages" => {
+ "ruby" => {
+ "platform" => "centos",
+ "version" => "2.0.0",
+ "target" => "uno"
+ }
+ }
+ }
+ },
+ { "name" => "my-name-is-butters",
+ "chef_environment" => "moon",
+ "platform" => "rhel",
+ "automatic" => {
+ "languages" => {
+ "ruby" => {
+ "platform" => "solaris2",
+ "version" => "2.1.2",
+ "target" => "random"
+ }
+ }
+ }
+ },
+ ],
+ "start" => 0,
+ "total" => 4
+ }
+ @rest.stub(:get_rest).and_return(@response)
+ end
+
+ it "should accept a type as the first argument" do
+ lambda { @query.search("node") }.should_not raise_error
+ lambda { @query.search(:node) }.should_not raise_error
+ lambda { @query.search(Hash.new) }.should raise_error(ArgumentError)
+ end
+
+ it "should query for every object of a type by default" do
+ @rest.should_receive(:get_rest).with("search/node?q=*:*&sort=X_CHEF_id_CHEF_X%20asc&start=0&rows=1000").and_return(@response)
+ @query = Chef::Search::Query.new
+ @query.search(:node)
+ end
+
+ it "should allow a custom query" do
+ @rest.should_receive(:get_rest).with("search/node?q=platform:rhel&sort=X_CHEF_id_CHEF_X%20asc&start=0&rows=1000").and_return(@response)
+ @query = Chef::Search::Query.new
+ @query.search(:node, "platform:rhel")
+ end
+
+ it "should let you set a sort order" do
+ @rest.should_receive(:get_rest).with("search/node?q=platform:rhel&sort=id%20desc&start=0&rows=1000").and_return(@response)
+ @query = Chef::Search::Query.new
+ args = Hash.new
+ args[:sort] = "id desc"
+ @query.search(:node, "platform:rhel", args)
+ end
+
+ it "should let you set a starting object" do
+ @rest.should_receive(:get_rest).with("search/node?q=platform:rhel&sort=id%20desc&start=2&rows=1000").and_return(@response)
+ @query = Chef::Search::Query.new
+ args = Hash.new
+ args[:sort] = "id desc"
+ args[:start] = 2
+ @query.search(:node, "platform:rhel", args)
+ end
+
+ it "should let you set how many rows to return" do
+ @rest.should_receive(:get_rest).with("search/node?q=platform:rhel&sort=id%20desc&start=2&rows=40").and_return(@response)
+ @query = Chef::Search::Query.new
+ args = Hash.new
+ args[:sort] = "id desc"
+ args[:start] = 2
+ args[:rows] = 40
+ @query.search(:node, "platform:rhel", args)
+ end
+
+ it "should return the raw rows, start, and total if no block is passed" do
+ rows, start, total = @query.search(:node)
+ rows.should equal(@response["rows"])
+ start.should equal(@response["start"])
+ total.should equal(@response["total"])
+ end
+
+ it "should call a block for each object in the response" do
+ @call_me = double("blocky")
+ @response["rows"].each { |r| @call_me.should_receive(:do).with(r) }
+ @query.search(:node) { |r| @call_me.do(r) }
+ end
+
+ it "should page through the responses" do
+ @call_me = double("blocky")
+ @response["rows"].each { |r| @call_me.should_receive(:do).with(r) }
+ args = Hash.new
+ args[:sort] = nil
+ args[:start] = 0
+ args[:rows] = 1
+ @query.search(:node, "*:*", args) { |r| @call_me.do(r) }
+ end
+ end
+
+ # filtered search results should only return the things asked for
+ describe "new search" do
+ before(:each) do
+ @response = {
+ "rows" => [
+ { "url" => "my-url-is-node",
+ "data" => {
+ "env" => "elysium",
+ "ruby_plat" => "i386-mingw32"
+ }
+ },
+ { "url" => "my-url-is-jonas",
+ "data" => {
+ "env" => "hades",
+ "ruby_plat" => "i386-mingw32"
+ }
+ },
+ { "url" => "my-url-is-flipper",
+ "data" => {
+ "env" => "elysium",
+ "ruby_plat" => "centos"
+ }
+ },
+ { "url" => "my-url-is-butters",
+ "data" => {
+ "env" => "moon",
+ "ruby_plat" => "solaris2"
+ }
+ },
+ ],
+ "start" => 0,
+ "total" => 4
+ }
+ @rest.stub(:post_rest).and_return(@response)
+ end
+
+ it "should allow you to filter search results" do
+ filter_args = Hash.new
+ filter_args[:env] = ["chef_environment"]
+ filter_args[:ruby_plat] = ["languages", "ruby", "platform"]
+ @rest.should_receive(:post_rest).with("search/node?q=platform:rhel&sort=X_CHEF_id_CHEF_X%20asc&start=0&rows=1000", filter_args).and_return(@response)
+ @query = Chef::Search::Query.new
+ args = Hash.new
+ args[:filter_result] = filter_args
+ @query.search(:node, "platform:rhel", args)
end
end
end