chef-solr 0.9.18 → 0.10.0.beta.0
Sign up to get free protection for your applications and to get access to all the features.
- data/bin/{chef-solr-indexer → chef-solr-installer} +4 -7
- data/lib/chef/solr.rb +1 -234
- data/lib/chef/solr/application/solr.rb +71 -43
- data/lib/chef/solr/solr_installer.rb +385 -0
- data/lib/chef/solr/version.rb +7 -1
- data/solr/solr-home.tar.gz +0 -0
- data/solr/solr-jetty.tar.gz +0 -0
- metadata +11 -41
- data/lib/chef/solr/application/indexer.rb +0 -148
- data/lib/chef/solr/index.rb +0 -103
- data/lib/chef/solr/index_queue_consumer.rb +0 -82
- data/lib/chef/solr/query.rb +0 -99
- data/spec/chef/solr/index_spec.rb +0 -187
- data/spec/chef/solr/query_spec.rb +0 -14
- data/spec/chef/solr_spec.rb +0 -301
- data/spec/spec.opts +0 -1
- data/spec/spec_helper.rb +0 -14
@@ -1,7 +1,7 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
#
|
3
|
-
# Author::
|
4
|
-
# Copyright:: Copyright (c) 2009 Opscode, Inc.
|
3
|
+
# Author:: Daniel DeLeo (<dan@opscode.com>)
|
4
|
+
# Copyright:: Copyright (c) 2009, 2011 Opscode, Inc.
|
5
5
|
# License:: Apache License, Version 2.0
|
6
6
|
#
|
7
7
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
@@ -18,11 +18,8 @@
|
|
18
18
|
#
|
19
19
|
|
20
20
|
require 'rubygems'
|
21
|
-
|
22
21
|
$:.unshift(File.expand_path(File.join(File.dirname(__FILE__), "..", "lib")))
|
23
|
-
|
24
|
-
|
25
|
-
require 'chef/solr/application/indexer'
|
22
|
+
require 'chef/solr/solr_installer'
|
26
23
|
|
27
|
-
Chef::
|
24
|
+
Chef::SolrInstaller.new(ARGV).run
|
28
25
|
|
data/lib/chef/solr.rb
CHANGED
@@ -1,234 +1 @@
|
|
1
|
-
|
2
|
-
# Author:: Adam Jacob (<adam@opscode.com>)
|
3
|
-
# Copyright:: Copyright (c) 2009 Opscode, Inc.
|
4
|
-
# License:: Apache License, Version 2.0
|
5
|
-
#
|
6
|
-
# Licensed under the Apache License, Version 2.0 (the "License");
|
7
|
-
# you may not use this file except in compliance with the License.
|
8
|
-
# You may obtain a copy of the License at
|
9
|
-
#
|
10
|
-
# http://www.apache.org/licenses/LICENSE-2.0
|
11
|
-
#
|
12
|
-
# Unless required by applicable law or agreed to in writing, software
|
13
|
-
# distributed under the License is distributed on an "AS IS" BASIS,
|
14
|
-
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
15
|
-
# See the License for the specific language governing permissions and
|
16
|
-
# limitations under the License.
|
17
|
-
#
|
18
|
-
|
19
|
-
require 'chef/mixin/xml_escape'
|
20
|
-
require 'chef/log'
|
21
|
-
require 'chef/config'
|
22
|
-
require 'chef/couchdb'
|
23
|
-
require 'net/http'
|
24
|
-
require 'libxml'
|
25
|
-
require 'uri'
|
26
|
-
|
27
|
-
class Chef
|
28
|
-
class Solr
|
29
|
-
|
30
|
-
include Chef::Mixin::XMLEscape
|
31
|
-
|
32
|
-
attr_accessor :solr_url, :http
|
33
|
-
|
34
|
-
def initialize(solr_url=Chef::Config[:solr_url])
|
35
|
-
@solr_url = solr_url
|
36
|
-
uri = URI.parse(@solr_url)
|
37
|
-
@http = Net::HTTP.new(uri.host, uri.port)
|
38
|
-
end
|
39
|
-
|
40
|
-
def solr_select(database, type, options={})
|
41
|
-
options[:wt] = :ruby
|
42
|
-
options[:indent] = "off"
|
43
|
-
options[:fq] = if type.kind_of?(Array)
|
44
|
-
"+X_CHEF_database_CHEF_X:#{database} +X_CHEF_type_CHEF_X:#{type[0]} +data_bag:#{type[1]}"
|
45
|
-
else
|
46
|
-
"+X_CHEF_database_CHEF_X:#{database} +X_CHEF_type_CHEF_X:#{type}"
|
47
|
-
end
|
48
|
-
select_url = "/solr/select?#{to_params(options)}"
|
49
|
-
Chef::Log.debug("Sending #{select_url} to Solr")
|
50
|
-
req = Net::HTTP::Get.new(select_url)
|
51
|
-
|
52
|
-
description = "Search Query to Solr '#{solr_url}#{select_url}'"
|
53
|
-
|
54
|
-
res = http_request_handler(req, description)
|
55
|
-
Chef::Log.debug("Parsing Solr result set:\n#{res.body}")
|
56
|
-
eval(res.body)
|
57
|
-
end
|
58
|
-
|
59
|
-
def post_to_solr(doc)
|
60
|
-
Chef::Log.debug("POSTing document to SOLR:\n#{doc}")
|
61
|
-
req = Net::HTTP::Post.new("/solr/update", "Content-Type" => "text/xml")
|
62
|
-
req.body = doc.to_s
|
63
|
-
|
64
|
-
description = "POST to Solr '#{solr_url}'"
|
65
|
-
|
66
|
-
http_request_handler(req, description)
|
67
|
-
end
|
68
|
-
|
69
|
-
START_XML = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<add><doc>"
|
70
|
-
END_XML = "</doc></add>\n"
|
71
|
-
FIELD_ATTR = '<field name="'
|
72
|
-
FIELD_ATTR_END = '">'
|
73
|
-
CLOSE_FIELD = "</field>"
|
74
|
-
|
75
|
-
def solr_add(data)
|
76
|
-
Chef::Log.debug("adding to SOLR: #{data.inspect}")
|
77
|
-
|
78
|
-
xml = ""
|
79
|
-
xml << START_XML
|
80
|
-
|
81
|
-
data.each do |field, values|
|
82
|
-
values.each do |v|
|
83
|
-
xml << FIELD_ATTR
|
84
|
-
xml << xml_escape(field)
|
85
|
-
xml << FIELD_ATTR_END
|
86
|
-
xml << xml_escape(v)
|
87
|
-
xml << CLOSE_FIELD
|
88
|
-
end
|
89
|
-
end
|
90
|
-
xml << END_XML
|
91
|
-
xml
|
92
|
-
|
93
|
-
post_to_solr(xml)
|
94
|
-
end
|
95
|
-
|
96
|
-
def solr_commit(opts={})
|
97
|
-
post_to_solr(generate_single_element("commit", opts))
|
98
|
-
end
|
99
|
-
|
100
|
-
def solr_optimize(opts={})
|
101
|
-
post_to_solr(generate_single_element("optimize", opts))
|
102
|
-
end
|
103
|
-
|
104
|
-
def solr_rollback
|
105
|
-
post_to_solr(generate_single_element("rollback"))
|
106
|
-
end
|
107
|
-
|
108
|
-
def solr_delete_by_id(ids)
|
109
|
-
post_to_solr(generate_delete_document("id", ids))
|
110
|
-
end
|
111
|
-
|
112
|
-
def solr_delete_by_query(queries)
|
113
|
-
post_to_solr(generate_delete_document("query", queries))
|
114
|
-
end
|
115
|
-
|
116
|
-
def rebuild_index(url=Chef::Config[:couchdb_url], db=Chef::Config[:couchdb_database])
|
117
|
-
solr_delete_by_query("X_CHEF_database_CHEF_X:#{db}")
|
118
|
-
solr_commit
|
119
|
-
|
120
|
-
results = {}
|
121
|
-
[Chef::ApiClient, Chef::Node, Chef::Role].each do |klass|
|
122
|
-
results[klass.name] = reindex_all(klass) ? "success" : "failed"
|
123
|
-
end
|
124
|
-
databags = Chef::DataBag.cdb_list(true)
|
125
|
-
Chef::Log.info("Reloading #{databags.size.to_s} #{Chef::DataBag} objects into the indexer")
|
126
|
-
databags.each { |i| i.add_to_index; i.list(true).each { |x| x.add_to_index } }
|
127
|
-
results[Chef::DataBag.name] = "success"
|
128
|
-
results
|
129
|
-
end
|
130
|
-
|
131
|
-
private
|
132
|
-
|
133
|
-
def reindex_all(klass, metadata={})
|
134
|
-
begin
|
135
|
-
items = klass.cdb_list(true)
|
136
|
-
Chef::Log.info("Reloading #{items.size.to_s} #{klass.name} objects into the indexer")
|
137
|
-
items.each { |i| i.add_to_index }
|
138
|
-
rescue Net::HTTPServerException => e
|
139
|
-
# 404s are okay, there might not be any of that kind of object...
|
140
|
-
if e.message =~ /Not Found/
|
141
|
-
Chef::Log.warn("Could not load #{klass.name} objects from couch for re-indexing (this is ok if you don't have any of these)")
|
142
|
-
return false
|
143
|
-
else
|
144
|
-
raise e
|
145
|
-
end
|
146
|
-
rescue Exception => e
|
147
|
-
Chef::Log.fatal("Chef encountered an error while attempting to load #{klass.name} objects back into the index")
|
148
|
-
raise e
|
149
|
-
end
|
150
|
-
true
|
151
|
-
end
|
152
|
-
|
153
|
-
def generate_single_element(elem, opts={})
|
154
|
-
xml_document = LibXML::XML::Document.new
|
155
|
-
xml_elem = LibXML::XML::Node.new(elem)
|
156
|
-
opts.each { |k,v| xml_elem[k.to_s] = xml_escape(v.to_s) }
|
157
|
-
xml_document.root = xml_elem
|
158
|
-
xml_document.to_s(:indent => false)
|
159
|
-
end
|
160
|
-
|
161
|
-
def generate_delete_document(type, list)
|
162
|
-
list = [list] unless list.is_a?(Array)
|
163
|
-
xml_document = LibXML::XML::Document.new
|
164
|
-
xml_delete = LibXML::XML::Node.new("delete")
|
165
|
-
xml_document.root = xml_delete
|
166
|
-
list.each do |id|
|
167
|
-
xml_id = LibXML::XML::Node.new(type)
|
168
|
-
xml_id.content = id.to_s
|
169
|
-
xml_delete << xml_id
|
170
|
-
end
|
171
|
-
xml_document.to_s(:indent => false)
|
172
|
-
end
|
173
|
-
|
174
|
-
# Thanks to Merb!
|
175
|
-
def to_params(params_hash)
|
176
|
-
params = ''
|
177
|
-
stack = []
|
178
|
-
|
179
|
-
params_hash.each do |k, v|
|
180
|
-
if v.is_a?(Hash)
|
181
|
-
stack << [k,v]
|
182
|
-
else
|
183
|
-
params << "#{k}=#{escape(v)}&"
|
184
|
-
end
|
185
|
-
end
|
186
|
-
|
187
|
-
stack.each do |parent, hash|
|
188
|
-
hash.each do |k, v|
|
189
|
-
if v.is_a?(Hash)
|
190
|
-
stack << ["#{parent}[#{k}]", escape(v)]
|
191
|
-
else
|
192
|
-
params << "#{parent}[#{k}]=#{escape(v)}&"
|
193
|
-
end
|
194
|
-
end
|
195
|
-
end
|
196
|
-
|
197
|
-
params.chop! # trailing &
|
198
|
-
params
|
199
|
-
end
|
200
|
-
|
201
|
-
# escapes a query key/value for http
|
202
|
-
# Thanks to RSolr!
|
203
|
-
def escape(s)
|
204
|
-
s.to_s.gsub(/([^ a-zA-Z0-9_.-]+)/n) {
|
205
|
-
'%'+$1.unpack('H2'*$1.size).join('%').upcase
|
206
|
-
}.tr(' ', '+')
|
207
|
-
end
|
208
|
-
|
209
|
-
# handles multiple net/http exceptions and no method closed? bug
|
210
|
-
def http_request_handler(req, description='HTTP call')
|
211
|
-
res = @http.request(req)
|
212
|
-
unless res.kind_of?(Net::HTTPSuccess)
|
213
|
-
Chef::Log.fatal("#{description} failed (#{res.class} #{res.code} #{res.message})")
|
214
|
-
res.error!
|
215
|
-
end
|
216
|
-
res
|
217
|
-
rescue Timeout::Error, Errno::EINVAL, EOFError, Net::HTTPBadResponse, Net::HTTPHeaderSyntaxError, Net::ProtocolError, Errno::ECONNREFUSED, Errno::ECONNRESET, Errno::ETIMEDOUT, NoMethodError => e
|
218
|
-
# http://redmine.ruby-lang.org/issues/show/2708
|
219
|
-
# http://redmine.ruby-lang.org/issues/show/2758
|
220
|
-
if e.to_s =~ /#{Regexp.escape(%q|undefined method 'closed?' for nil:NilClass|)}/
|
221
|
-
Chef::Log.fatal("#{description} failed. Chef::Exceptions::SolrConnectionError exception: Errno::ECONNREFUSED (net/http undefined method closed?) attempting to contact #{@solr_url}")
|
222
|
-
Chef::Log.debug("rescued error in http connect, treating it as Errno::ECONNREFUSED to hide bug in net/http")
|
223
|
-
Chef::Log.debug(e.backtrace.join("\n"))
|
224
|
-
raise Chef::Exceptions::SolrConnectionError, "Errno::ECONNREFUSED: Connection refused attempting to contact #{@solr_url}"
|
225
|
-
end
|
226
|
-
|
227
|
-
Chef::Log.fatal("#{description} failed. Chef::Exceptions::SolrConnectionError exception: #{e.class.name}: #{e.to_s} attempting to contact #{@solr_url}")
|
228
|
-
Chef::Log.debug(e.backtrace.join("\n"))
|
229
|
-
|
230
|
-
raise Chef::Exceptions::SolrConnectionError, "#{e.class.name}: #{e.to_s}"
|
231
|
-
end
|
232
|
-
|
233
|
-
end
|
234
|
-
end
|
1
|
+
require 'chef/solr/version'
|
@@ -15,12 +15,12 @@
|
|
15
15
|
# See the License for the specific language governing permissions and
|
16
16
|
# limitations under the License.
|
17
17
|
|
18
|
-
|
18
|
+
|
19
|
+
require 'rexml/document'
|
19
20
|
require 'chef/log'
|
20
21
|
require 'chef/config'
|
21
22
|
require 'chef/application'
|
22
23
|
require 'chef/daemon'
|
23
|
-
require 'chef/client'
|
24
24
|
require 'chef/solr'
|
25
25
|
|
26
26
|
class Chef
|
@@ -116,7 +116,76 @@ class Chef
|
|
116
116
|
super
|
117
117
|
end
|
118
118
|
|
119
|
+
def schema_file_path
|
120
|
+
@schema_file_path ||= File.join(Chef::Config[:solr_home_path], 'conf', 'schema.xml')
|
121
|
+
end
|
122
|
+
|
123
|
+
|
124
|
+
def schema_document
|
125
|
+
@schema_document ||= begin
|
126
|
+
File.open(schema_file_path, 'r') do |xmlsux|
|
127
|
+
REXML::Document.new(xmlsux)
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
def schema_attributes
|
133
|
+
@schema_attributes ||= REXML::XPath.first(schema_document, '/schema').attributes
|
134
|
+
end
|
135
|
+
|
136
|
+
def solr_schema_name
|
137
|
+
schema_attributes["name"]
|
138
|
+
end
|
139
|
+
|
140
|
+
def solr_schema_version
|
141
|
+
schema_attributes["version"]
|
142
|
+
end
|
143
|
+
|
144
|
+
def valid_schema_name?
|
145
|
+
solr_schema_name == Chef::Solr::SCHEMA_NAME
|
146
|
+
end
|
147
|
+
|
148
|
+
def valid_schema_version?
|
149
|
+
solr_schema_version == Chef::Solr::SCHEMA_VERSION
|
150
|
+
end
|
151
|
+
|
152
|
+
def solr_home_exist?
|
153
|
+
File.directory?(Chef::Config[:solr_home_path])
|
154
|
+
end
|
155
|
+
|
156
|
+
def solr_data_dir_exist?
|
157
|
+
File.directory?(Chef::Config[:solr_data_path])
|
158
|
+
end
|
159
|
+
|
160
|
+
def solr_jetty_home_exist?
|
161
|
+
File.directory?(Chef::Config[:solr_jetty_path])
|
162
|
+
end
|
163
|
+
|
164
|
+
def assert_solr_installed!
|
165
|
+
unless solr_home_exist? && solr_data_dir_exist? && solr_jetty_home_exist?
|
166
|
+
Chef::Log.fatal "Chef Solr is not installed or solr_home_path, solr_data_path, and solr_jetty_path are misconfigured."
|
167
|
+
Chef::Log.fatal "Your current configuration is:"
|
168
|
+
Chef::Log.fatal "solr_home_path: #{Chef::Config[:solr_home_path]}"
|
169
|
+
Chef::Log.fatal "solr_data_path: #{Chef::Config[:solr_data_path]}"
|
170
|
+
Chef::Log.fatal "solr_jetty_path: #{Chef::Config[:solr_jetty_path]}"
|
171
|
+
Chef::Log.fatal "You can install Chef Solr using the chef-solr-installer script."
|
172
|
+
exit 1
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
def assert_valid_schema!
|
177
|
+
unless valid_schema_name? && valid_schema_version?
|
178
|
+
Chef::Log.fatal "Your Chef Solr installation needs to be upgraded."
|
179
|
+
Chef::Log.fatal "Expected schema version #{Chef::Solr::SCHEMA_VERSION} but version #{solr_schema_version} is installed."
|
180
|
+
Chef::Log.fatal "Use chef-solr-installer to upgrade your Solr install after backing up your data."
|
181
|
+
exit 1
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
119
185
|
def setup_application
|
186
|
+
assert_solr_installed!
|
187
|
+
assert_valid_schema!
|
188
|
+
|
120
189
|
# Need to redirect stdout and stderr so Java process inherits them.
|
121
190
|
# If -L wasn't specified, Chef::Config[:log_location] will be an IO
|
122
191
|
# object, otherwise it will be a String.
|
@@ -128,47 +197,7 @@ class Chef
|
|
128
197
|
|
129
198
|
Chef::Log.level = Chef::Config[:log_level]
|
130
199
|
|
131
|
-
# Build up a client
|
132
|
-
node = Chef::Node.new
|
133
|
-
node.platform = :default
|
134
|
-
node.platform_version = 42
|
135
|
-
|
136
200
|
Chef::Daemon.change_privilege
|
137
|
-
|
138
|
-
solr_base = File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "..", "..", "solr"))
|
139
|
-
|
140
|
-
run_context = Chef::RunContext.new(node, {})
|
141
|
-
# Create the Jetty container
|
142
|
-
unless File.directory?(Chef::Config[:solr_jetty_path])
|
143
|
-
Chef::Log.warn("Initializing the Jetty container")
|
144
|
-
solr_jetty_dir = Chef::Resource::Directory.new(Chef::Config[:solr_jetty_path], run_context)
|
145
|
-
solr_jetty_dir.recursive(true)
|
146
|
-
solr_jetty_dir.run_action(:create)
|
147
|
-
solr_jetty_untar = Chef::Resource::Execute.new("untar_jetty", run_context)
|
148
|
-
solr_jetty_untar.command("tar zxvf #{File.join(solr_base, 'solr-jetty.tar.gz')}")
|
149
|
-
solr_jetty_untar.cwd(Chef::Config[:solr_jetty_path])
|
150
|
-
solr_jetty_untar.run_action(:run)
|
151
|
-
end
|
152
|
-
|
153
|
-
# Create the solr home
|
154
|
-
unless File.directory?(Chef::Config[:solr_home_path])
|
155
|
-
Chef::Log.warn("Initializing Solr home directory")
|
156
|
-
solr_home_dir = Chef::Resource::Directory.new(Chef::Config[:solr_home_path], run_context)
|
157
|
-
solr_home_dir.recursive(true)
|
158
|
-
solr_home_dir.run_action(:create)
|
159
|
-
solr_jetty_untar = Chef::Resource::Execute.new("untar_solr_home", run_context)
|
160
|
-
solr_jetty_untar.command("tar zxvf #{File.join(solr_base, 'solr-home.tar.gz')}")
|
161
|
-
solr_jetty_untar.cwd(Chef::Config[:solr_home_path])
|
162
|
-
solr_jetty_untar.run_action(:run)
|
163
|
-
end
|
164
|
-
|
165
|
-
# Create the solr data path
|
166
|
-
unless File.directory?(Chef::Config[:solr_data_path])
|
167
|
-
Chef::Log.warn("Initializing Solr data directory")
|
168
|
-
solr_data_dir = Chef::Resource::Directory.new(Chef::Config[:solr_data_path], run_context)
|
169
|
-
solr_data_dir.recursive(true)
|
170
|
-
solr_data_dir.run_action(:create)
|
171
|
-
end
|
172
201
|
end
|
173
202
|
|
174
203
|
def run_application
|
@@ -191,7 +220,6 @@ class Chef
|
|
191
220
|
|
192
221
|
STDOUT.reopen(@logfile)
|
193
222
|
STDERR.reopen(@logfile)
|
194
|
-
@logfile.close
|
195
223
|
end
|
196
224
|
|
197
225
|
Kernel.exec(command)
|
@@ -0,0 +1,385 @@
|
|
1
|
+
#
|
2
|
+
# Author:: Daniel DeLeo (<dan@opscode.com)
|
3
|
+
# Copyright:: Copyright (c) 2011 Opscode, Inc.
|
4
|
+
# License:: Apache License, Version 2.0
|
5
|
+
#
|
6
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
7
|
+
# you may not use this file except in compliance with the License.
|
8
|
+
# You may obtain a copy of the License at
|
9
|
+
#
|
10
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
11
|
+
#
|
12
|
+
# Unless required by applicable law or agreed to in writing, software
|
13
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
14
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
15
|
+
# See the License for the specific language governing permissions and
|
16
|
+
# limitations under the License.
|
17
|
+
|
18
|
+
require 'pp'
|
19
|
+
require 'optparse'
|
20
|
+
require 'chef/solr/version'
|
21
|
+
require 'chef/shell_out'
|
22
|
+
require 'chef/mixin/shell_out'
|
23
|
+
|
24
|
+
class Chef
|
25
|
+
class SolrInstaller
|
26
|
+
|
27
|
+
class Config
|
28
|
+
class CompatConfig
|
29
|
+
def initialize
|
30
|
+
@config_settings = {}
|
31
|
+
end
|
32
|
+
|
33
|
+
def from_file(file)
|
34
|
+
file = File.expand_path(file)
|
35
|
+
if File.readable?(file)
|
36
|
+
instance_eval(IO.read(file), file, 1)
|
37
|
+
else
|
38
|
+
STDERR.puts "Cannot open config file #{file} default settings will be used"
|
39
|
+
end
|
40
|
+
self
|
41
|
+
end
|
42
|
+
|
43
|
+
def method_missing(method_name, *args, &block)
|
44
|
+
if args.size == 1
|
45
|
+
@config_settings[method_name] = args.first
|
46
|
+
elsif args.empty?
|
47
|
+
@config_settings[method_name] or super
|
48
|
+
else
|
49
|
+
super
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def to_hash
|
54
|
+
@config_settings
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
|
59
|
+
def self.default_values
|
60
|
+
@default_values ||= {}
|
61
|
+
end
|
62
|
+
|
63
|
+
def self.configurables
|
64
|
+
@configurables ||= []
|
65
|
+
end
|
66
|
+
|
67
|
+
def self.configurable(value, default=nil)
|
68
|
+
configurables << value
|
69
|
+
attr_accessor value
|
70
|
+
default_values[value] = default if default
|
71
|
+
end
|
72
|
+
|
73
|
+
def each_configurable
|
74
|
+
self.class.configurables.each do |config_param|
|
75
|
+
yield [config_param, send(config_param)]
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
configurable :config_file, '/etc/chef/solr.rb'
|
80
|
+
|
81
|
+
# Defaults to /var/chef
|
82
|
+
configurable :solr_base_path, nil
|
83
|
+
|
84
|
+
def solr_base_path
|
85
|
+
@solr_base_path || '/var/chef'
|
86
|
+
end
|
87
|
+
|
88
|
+
# Sets the solr_base_path. Also resets solr_home_path, solr_jetty_path,
|
89
|
+
# and solr_data_path.
|
90
|
+
def solr_base_path=(base_path)
|
91
|
+
@solr_home_path, @solr_jetty_path, @solr_data_path = nil,nil,nil
|
92
|
+
@solr_base_path = base_path
|
93
|
+
end
|
94
|
+
|
95
|
+
|
96
|
+
# Computed from base path, defaults to /var/chef/solr
|
97
|
+
configurable :solr_home_path, nil
|
98
|
+
|
99
|
+
def solr_home_path
|
100
|
+
@solr_home_path || File.join(solr_base_path, 'solr')
|
101
|
+
end
|
102
|
+
|
103
|
+
# Computed from base path, defaults to /var/chef/solr-jetty
|
104
|
+
configurable :solr_jetty_path, nil
|
105
|
+
|
106
|
+
def solr_jetty_path
|
107
|
+
@solr_jetty_path || File.join(solr_base_path, 'solr-jetty')
|
108
|
+
end
|
109
|
+
|
110
|
+
# Computed from base path, defaults to /var/chef/solr/data
|
111
|
+
configurable :solr_data_path, nil
|
112
|
+
|
113
|
+
def solr_data_path
|
114
|
+
@solr_data_path || File.join(solr_base_path, 'solr', 'data')
|
115
|
+
end
|
116
|
+
|
117
|
+
|
118
|
+
configurable :user, nil
|
119
|
+
|
120
|
+
configurable :group, nil
|
121
|
+
|
122
|
+
configurable :force, false
|
123
|
+
|
124
|
+
alias :force? :force
|
125
|
+
|
126
|
+
configurable :noop, false
|
127
|
+
|
128
|
+
alias :noop? :noop
|
129
|
+
|
130
|
+
def initialize
|
131
|
+
apply_hash(self.class.default_values)
|
132
|
+
end
|
133
|
+
|
134
|
+
def configure_from(argv)
|
135
|
+
cli_config = CLI.parse_options(argv)
|
136
|
+
#pp :cli_config => cli_config.to_hash
|
137
|
+
config_file_config = CompatConfig.new.from_file(cli_config.config_file).to_hash
|
138
|
+
#pp :config_file_config => config_file_config
|
139
|
+
apply_hash(config_file_config)
|
140
|
+
apply_hash(cli_config.to_hash)
|
141
|
+
#pp :combined_config => self.to_hash
|
142
|
+
self
|
143
|
+
end
|
144
|
+
|
145
|
+
def to_hash
|
146
|
+
self.class.configurables.inject({}) do |hash, config_option|
|
147
|
+
value = instance_variable_get("@#{config_option}".to_sym)
|
148
|
+
hash[config_option] = value if value
|
149
|
+
hash
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
def apply_hash(hash)
|
154
|
+
hash.each do |key, value|
|
155
|
+
method_for_key = "#{key}=".to_sym
|
156
|
+
if respond_to?(method_for_key)
|
157
|
+
send(method_for_key, value)
|
158
|
+
else
|
159
|
+
STDERR.puts("Configuration setting #{key} is unknown and will be ignored")
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
module CLI
|
165
|
+
@config = Config.new
|
166
|
+
|
167
|
+
@option_parser = OptionParser.new do |o|
|
168
|
+
o.banner = "Usage: chef-solr-installer [options]"
|
169
|
+
|
170
|
+
o.on('-c', '--config CONFIG_FILE', 'The configuration file to use') do |conf|
|
171
|
+
@config.config_file = File.expand_path(conf)
|
172
|
+
end
|
173
|
+
|
174
|
+
o.on('-u', '--user USER', "User who will own Solr's data directory") do |u|
|
175
|
+
@config.user = u
|
176
|
+
end
|
177
|
+
|
178
|
+
o.on('-g', '--group GROUP', "Group that will own Solr's data directory") do |g|
|
179
|
+
@config.group = g
|
180
|
+
end
|
181
|
+
|
182
|
+
o.on('-p', '--base-path PATH', "The base path for the installation. Must be given before any -H -W or -D options") do |path|
|
183
|
+
@config.solr_base_path = path
|
184
|
+
end
|
185
|
+
|
186
|
+
o.on('-H', '--solr-home-dir PATH', 'Where to create the Solr home directory. Defaults to BASE_PATH/solr') do |path|
|
187
|
+
@config.solr_home_path = path
|
188
|
+
end
|
189
|
+
|
190
|
+
o.on('-W', '--solr-jetty-path PATH', 'Where to install Jetty for Solr. Defaults to BASE_PATH/solr-jetty ') do |path|
|
191
|
+
@config.solr_jetty_path = path
|
192
|
+
end
|
193
|
+
|
194
|
+
o.on('-D', '--solr-data-path PATH', 'Where to create the Solr data directory. Defaults to BASE_PATH/solr/data') do |path|
|
195
|
+
@config.solr_data_path = path
|
196
|
+
end
|
197
|
+
|
198
|
+
o.on('-n', '--noop', "Don't actually install, just show what would be done by the install") do
|
199
|
+
@config.noop = true
|
200
|
+
end
|
201
|
+
|
202
|
+
o.on('-f', '--force', 'Overwrite any existing installation without asking for confirmation') do
|
203
|
+
@config.force = true
|
204
|
+
end
|
205
|
+
|
206
|
+
o.on_tail('-h', '--help', 'show this message') do
|
207
|
+
puts "chef-solr-installer #{Chef::Solr::VERSION}"
|
208
|
+
puts ''
|
209
|
+
puts o
|
210
|
+
puts ''
|
211
|
+
puts 'Default Settings:'
|
212
|
+
@config.each_configurable do |param, value|
|
213
|
+
value_for_display = value || "none/false"
|
214
|
+
puts " #{param}:".ljust(20) + " #{value_for_display}"
|
215
|
+
end
|
216
|
+
exit 1
|
217
|
+
end
|
218
|
+
|
219
|
+
o.on_tail('-v', '--version', 'show the version and exit') do
|
220
|
+
puts "chef-solr-installer #{Chef::Solr::VERSION}"
|
221
|
+
exit 0
|
222
|
+
end
|
223
|
+
|
224
|
+
end
|
225
|
+
|
226
|
+
def self.parse_options(argv)
|
227
|
+
@option_parser.parse!(argv.dup)
|
228
|
+
@config
|
229
|
+
end
|
230
|
+
|
231
|
+
def self.config
|
232
|
+
@config
|
233
|
+
end
|
234
|
+
|
235
|
+
end
|
236
|
+
|
237
|
+
end
|
238
|
+
|
239
|
+
include Chef::Mixin::ShellOut
|
240
|
+
|
241
|
+
PACKAGED_SOLR_DIR = File.expand_path( "../../../../solr", __FILE__)
|
242
|
+
|
243
|
+
attr_reader :config
|
244
|
+
|
245
|
+
def initialize(argv)
|
246
|
+
@indent = 0
|
247
|
+
@config = Config.new.configure_from(argv.dup)
|
248
|
+
@overwriting = false
|
249
|
+
end
|
250
|
+
|
251
|
+
def overwriting?
|
252
|
+
@overwriting
|
253
|
+
end
|
254
|
+
|
255
|
+
def chef_solr_installed?
|
256
|
+
File.exist?(config.solr_home_path)
|
257
|
+
end
|
258
|
+
|
259
|
+
def run
|
260
|
+
say ''
|
261
|
+
say "*** DRY RUN ***" if config.noop?
|
262
|
+
|
263
|
+
if chef_solr_installed?
|
264
|
+
@overwriting = true
|
265
|
+
confirm_overwrite unless config.force? || config.noop?
|
266
|
+
scorch_the_earth
|
267
|
+
end
|
268
|
+
|
269
|
+
create_solr_home
|
270
|
+
create_solr_data_dir
|
271
|
+
unpack_solr_jetty
|
272
|
+
|
273
|
+
say ""
|
274
|
+
say "Successfully installed Chef Solr."
|
275
|
+
|
276
|
+
if overwriting?
|
277
|
+
say "You can restore your search index using `knife index rebuild`"
|
278
|
+
end
|
279
|
+
end
|
280
|
+
|
281
|
+
def confirm_overwrite
|
282
|
+
if STDIN.tty? && STDOUT.tty?
|
283
|
+
say "Chef Solr is already installed in #{config.solr_home_path}"
|
284
|
+
print "Do you want to overwrite the current install? All existing Solr data will be lost. [y/n] "
|
285
|
+
unless STDIN.gets =~ /^y/
|
286
|
+
say "Quitting. Try running this with --noop to see what it will change."
|
287
|
+
exit 1
|
288
|
+
end
|
289
|
+
else
|
290
|
+
say(<<-FAIL)
|
291
|
+
ERROR: Chef Solr is already installed in #{config.solr_home_path} and you did not use the
|
292
|
+
--force option. Use --force to overwrite an existing installation in a non-
|
293
|
+
interactive terminal.
|
294
|
+
FAIL
|
295
|
+
exit 1
|
296
|
+
end
|
297
|
+
end
|
298
|
+
|
299
|
+
def scorch_the_earth
|
300
|
+
group("Removing the existing Chef Solr installation") do
|
301
|
+
rm_rf(config.solr_home_path)
|
302
|
+
rm_rf(config.solr_jetty_path)
|
303
|
+
rm_rf(config.solr_data_path)
|
304
|
+
end
|
305
|
+
end
|
306
|
+
|
307
|
+
def create_solr_home
|
308
|
+
group("Creating Solr Home Directory") do
|
309
|
+
mkdir_p(config.solr_home_path)
|
310
|
+
chdir(config.solr_home_path) do
|
311
|
+
sh("tar zxvf #{File.join(PACKAGED_SOLR_DIR, 'solr-home.tar.gz')}")
|
312
|
+
end
|
313
|
+
end
|
314
|
+
end
|
315
|
+
|
316
|
+
def create_solr_data_dir
|
317
|
+
group("Creating Solr Data Directory") do
|
318
|
+
mkdir_p(config.solr_data_path)
|
319
|
+
chown(config.solr_data_path)
|
320
|
+
end
|
321
|
+
end
|
322
|
+
|
323
|
+
def unpack_solr_jetty
|
324
|
+
group("Unpacking Solr Jetty") do
|
325
|
+
mkdir_p(config.solr_jetty_path)
|
326
|
+
chdir(config.solr_jetty_path) do
|
327
|
+
sh("tar zxvf #{File.join(PACKAGED_SOLR_DIR, 'solr-jetty.tar.gz')}")
|
328
|
+
end
|
329
|
+
end
|
330
|
+
end
|
331
|
+
|
332
|
+
def mkdir_p(directory)
|
333
|
+
say "mkdir -p #{directory}"
|
334
|
+
FileUtils.mkdir_p(directory, :mode => 0755) unless config.noop?
|
335
|
+
end
|
336
|
+
|
337
|
+
def chdir(dir, &block)
|
338
|
+
say "entering #{dir}"
|
339
|
+
if config.noop?
|
340
|
+
yield if block_given? # still call the block so we get the noop output.
|
341
|
+
else
|
342
|
+
Dir.chdir(dir) { yield if block_given? }
|
343
|
+
end
|
344
|
+
end
|
345
|
+
|
346
|
+
def sh(*args)
|
347
|
+
opts = args[1, args.size - 1]
|
348
|
+
opts_msg = opts.empty? ? '' : " #{opts.to_s}"
|
349
|
+
say "#{args.first}#{opts_msg}"
|
350
|
+
shell_out!(*(args << {:cwd => false})) unless config.noop?
|
351
|
+
end
|
352
|
+
|
353
|
+
def chown(file)
|
354
|
+
if config.user
|
355
|
+
msg = "chown #{config.user}"
|
356
|
+
msg << ":#{config.group}" if config.group
|
357
|
+
msg << " #{file}"
|
358
|
+
say msg
|
359
|
+
FileUtils.chown(config.user, config.group) unless config.noop?
|
360
|
+
end
|
361
|
+
end
|
362
|
+
|
363
|
+
def rm_rf(path)
|
364
|
+
say "rm -rf #{path}"
|
365
|
+
FileUtils.rm_rf(path) unless config.noop?
|
366
|
+
end
|
367
|
+
|
368
|
+
def indent
|
369
|
+
@indent += 1
|
370
|
+
yield
|
371
|
+
@indent -= 1
|
372
|
+
end
|
373
|
+
|
374
|
+
def group(message, &block)
|
375
|
+
say(message)
|
376
|
+
indent(&block)
|
377
|
+
end
|
378
|
+
|
379
|
+
def say(message)
|
380
|
+
puts "#{' ' * (2 * @indent)}#{message}"
|
381
|
+
end
|
382
|
+
|
383
|
+
end
|
384
|
+
end
|
385
|
+
|