1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
|
#
# Author:: Adam Jacob (<adam@opscode.com>)
# Copyright:: Copyright (c) 2009 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.
#
require 'chef/knife'
require 'chef/knife/core/node_presenter'
class Chef
class Knife
class Search < Knife
include Knife::Core::MultiAttributeReturnOption
deps do
require 'chef/node'
require 'chef/environment'
require 'chef/api_client'
require 'chef/search/query'
end
include Knife::Core::NodeFormattingOptions
banner "knife search INDEX QUERY (options)"
option :sort,
:short => "-o SORT",
:long => "--sort SORT",
:description => "The order to sort the results in",
:default => nil
option :start,
:short => "-b ROW",
:long => "--start ROW",
:description => "The row to start returning results at",
:default => 0,
:proc => lambda { |i| i.to_i }
option :rows,
:short => "-R INT",
:long => "--rows INT",
:description => "The number of rows to return",
:default => 1000,
:proc => lambda { |i| i.to_i }
option :run_list,
:short => "-r",
:long => "--run-list",
:description => "Show only the run list"
option :id_only,
:short => "-i",
:long => "--id-only",
:description => "Show only the ID of matching objects"
option :query,
:short => "-q QUERY",
: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 attributes of the matching objects; for example: \"ServerName=name, Kernel=kernel.version\""
def run
read_cli_args
fuzzify_query
if @type == 'node'
ui.use_presenter Knife::Core::NodePresenter
end
q = Chef::Search::Query.new
escaped_query = URI.escape(@query,
Regexp.new("[^#{URI::PATTERN::UNRESERVED}]"))
result_items = []
result_count = 0
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])
elsif (not ui.config[:attribute].nil?) && (not ui.config[:attribute].empty?)
search_args[:filter_result] = create_result_filter_from_attributes(ui.config[:attribute])
end
begin
q.search(@type, escaped_query, search_args) do |item|
formatted_item = Hash.new
if item.is_a?(Hash)
# doing a little magic here to set the correct name
formatted_item[item["data"]["__display_name"]] = item["data"]
formatted_item[item["data"]["__display_name"]].delete("__display_name")
else
formatted_item = format_for_display(item)
end
result_items << formatted_item
result_count += 1
end
rescue Net::HTTPServerException => e
msg = Chef::JSONCompat.from_json(e.response.body)["error"].first
ui.error("knife search failed: #{msg}")
exit 1
end
if ui.interchange?
output({:results => result_count, :rows => result_items})
else
ui.msg "#{result_count} items found"
ui.msg("\n")
result_items.each do |item|
output(item)
unless config[:id_only]
ui.msg("\n")
end
end
end
end
def read_cli_args
if config[:query]
if @name_args[1]
ui.error "please specify query as an argument or an option via -q, not both"
ui.msg opt_parser
exit 1
end
@type = name_args[0]
@query = config[:query]
else
case name_args.size
when 0
ui.error "no query specified"
ui.msg opt_parser
exit 1
when 1
@type = "node"
@query = name_args[0]
when 2
@type = name_args[0]
@query = name_args[1]
end
end
end
def fuzzify_query
if @query !~ /:/
@query = "tags:*#{@query}* OR roles:*#{@query}* OR fqdn:*#{@query}* OR addresses:*#{@query}*"
end
end
# This method turns a set of key value pairs in a string into the appropriate data structure that the
# chef-server search api is expecting.
# expected input is in the form of:
# -f "return_var1=path.to.attribute, return_var2=shorter.path"
#
# a more concrete example might be:
# -f "env=chef_environment, ruby_platform=languages.ruby.platform"
#
# The end result is a hash where the key is a symbol in the hash (the return variable)
# and the path is an array with the path elements as strings (in order)
# See lib/chef/search/query.rb for more examples of this.
def create_result_filter(filter_string)
final_filter = Hash.new
filter_string.gsub!(" ", "")
filters = filter_string.split(",")
filters.each do |f|
return_id, attr_path = f.split("=")
final_filter[return_id.to_sym] = attr_path.split(".")
end
return final_filter
end
def create_result_filter_from_attributes(filter_array)
final_filter = Hash.new
filter_array.each do |f|
final_filter[f] = f.split(".")
end
# adding magic filter so we can actually pull the name as before
final_filter["__display_name"] = [ "name" ]
return final_filter
end
end
end
end
|