solr_makr 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +14 -0
- data/.pryrc +3 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +19 -0
- data/Rakefile +2 -0
- data/bin/solr-makr +7 -0
- data/lib/files/schema.xml +255 -0
- data/lib/files/solrconfig.xml +667 -0
- data/lib/solr_makr.rb +42 -0
- data/lib/solr_makr/application.rb +49 -0
- data/lib/solr_makr/commands.rb +5 -0
- data/lib/solr_makr/commands/create_core.rb +21 -0
- data/lib/solr_makr/commands/destroy_core.rb +17 -0
- data/lib/solr_makr/commands/list_cores.rb +17 -0
- data/lib/solr_makr/commands/shared.rb +82 -0
- data/lib/solr_makr/core.rb +92 -0
- data/lib/solr_makr/core_status.rb +56 -0
- data/lib/solr_makr/solr_configuration.rb +78 -0
- data/lib/solr_makr/solr_request.rb +23 -0
- data/lib/solr_makr/version.rb +3 -0
- data/solr_makr.gemspec +30 -0
- metadata +200 -0
data/lib/solr_makr.rb
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
require "fileutils"
|
2
|
+
require "pathname"
|
3
|
+
|
4
|
+
class Pathname
|
5
|
+
alias_method :to_str, :to_s
|
6
|
+
end
|
7
|
+
|
8
|
+
require "active_support/concern"
|
9
|
+
require "active_support/configurable"
|
10
|
+
require "active_support/core_ext/object/blank"
|
11
|
+
require "active_support/core_ext/object/try"
|
12
|
+
require "active_support/core_ext/module/delegation"
|
13
|
+
require "attr_lazy"
|
14
|
+
require "commander"
|
15
|
+
require "nokogiri"
|
16
|
+
require "typhoeus"
|
17
|
+
require "virtus"
|
18
|
+
|
19
|
+
require "solr_makr/version"
|
20
|
+
require "solr_makr/solr_request"
|
21
|
+
require "solr_makr/core_status"
|
22
|
+
require "solr_makr/solr_configuration"
|
23
|
+
require "solr_makr/core"
|
24
|
+
require "solr_makr/commands"
|
25
|
+
require "solr_makr/commands/shared"
|
26
|
+
require "solr_makr/commands/create_core"
|
27
|
+
require "solr_makr/commands/destroy_core"
|
28
|
+
require "solr_makr/commands/list_cores"
|
29
|
+
require "solr_makr/application"
|
30
|
+
|
31
|
+
module SolrMakr
|
32
|
+
class << self
|
33
|
+
TEMPLATE_PATH = Pathname.new(File.dirname(__FILE__)).join('files')
|
34
|
+
|
35
|
+
# @param [String] path
|
36
|
+
# @raise [Errno::ENOENT] if undefined template
|
37
|
+
# @return [String] contents of file
|
38
|
+
def template(path)
|
39
|
+
TEMPLATE_PATH.join(path).read
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module SolrMakr
|
2
|
+
class Application
|
3
|
+
include Commander::Methods
|
4
|
+
|
5
|
+
NAME = 'solr-makr'
|
6
|
+
|
7
|
+
def run
|
8
|
+
program :name, NAME
|
9
|
+
program :version, SolrMakr::VERSION
|
10
|
+
program :description, 'Create a solr core programmatically'
|
11
|
+
program :help, 'Author', 'Alexa Grey <alexag@hranswerlink.com>'
|
12
|
+
program :help_formatter, Commander::HelpFormatter::TerminalCompact
|
13
|
+
|
14
|
+
default_command :help
|
15
|
+
|
16
|
+
global_option '-d', '--solr-home DIR', 'Path to the solr home directory'
|
17
|
+
global_option '-p', '--solr-port PORT', Integer, 'Port to use to communicate with the solr API'
|
18
|
+
global_option '-V', '--verbose', 'Show verbose output.'
|
19
|
+
|
20
|
+
command :create do |c|
|
21
|
+
c.syntax = "#{NAME} create NAME"
|
22
|
+
|
23
|
+
c.description = "Create and register a solr core."
|
24
|
+
|
25
|
+
c.when_called Commands::CreateCore, :run!
|
26
|
+
end
|
27
|
+
|
28
|
+
command :list do |c|
|
29
|
+
c.syntax = "#{NAME} list"
|
30
|
+
|
31
|
+
c.description = "List installed solr cores."
|
32
|
+
|
33
|
+
c.when_called Commands::ListCores, :run!
|
34
|
+
end
|
35
|
+
|
36
|
+
command :destroy do |c|
|
37
|
+
c.syntax = "#{NAME} destroy NAME"
|
38
|
+
|
39
|
+
c.option '--purge', 'Purge the solr core\'s instance directory'
|
40
|
+
|
41
|
+
c.description = "Unload and remove a solr core."
|
42
|
+
|
43
|
+
c.when_called Commands::DestroyCore, :run!
|
44
|
+
end
|
45
|
+
|
46
|
+
run!
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module SolrMakr
|
2
|
+
module Commands
|
3
|
+
class CreateCore
|
4
|
+
include Shared
|
5
|
+
|
6
|
+
config.acts_on_single_core = true
|
7
|
+
|
8
|
+
def run
|
9
|
+
with_message "initializing configuration" do
|
10
|
+
core.initialize_configuration!
|
11
|
+
end
|
12
|
+
|
13
|
+
with_message "registering new core with solr" do
|
14
|
+
core.register_with_solr!
|
15
|
+
end
|
16
|
+
|
17
|
+
say "Created core: #{core_name}"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module SolrMakr
|
2
|
+
module Commands
|
3
|
+
class DestroyCore
|
4
|
+
include Shared
|
5
|
+
|
6
|
+
config.acts_on_single_core = true
|
7
|
+
|
8
|
+
def run
|
9
|
+
with_message "registering new core with solr" do
|
10
|
+
core.unload_from_solr!(purge: options.purge)
|
11
|
+
end
|
12
|
+
|
13
|
+
say "Destroyed core: #{core_name}"
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module SolrMakr
|
2
|
+
module Commands
|
3
|
+
class ListCores
|
4
|
+
include Shared
|
5
|
+
|
6
|
+
def run
|
7
|
+
say "Listing core(s)."
|
8
|
+
|
9
|
+
cores = solr_config.solr_status
|
10
|
+
|
11
|
+
cores.each do |core|
|
12
|
+
say sprintf(" * %s -- %d document(s) / %s", core.name, core.documents, core.size)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
module SolrMakr
|
2
|
+
module Commands
|
3
|
+
module Shared
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
included do
|
7
|
+
include ActiveSupport::Configurable
|
8
|
+
|
9
|
+
config.core_name_index = 0
|
10
|
+
end
|
11
|
+
|
12
|
+
delegate :name, allow_nil: true, to: :core, prefix: true
|
13
|
+
|
14
|
+
# @!attribute [r] solr_config
|
15
|
+
# @return [SolrMakr::SolrConfiguration]
|
16
|
+
attr_reader :solr_config
|
17
|
+
|
18
|
+
# @!attribute [r] args
|
19
|
+
# @return [<String>]
|
20
|
+
attr_reader :args
|
21
|
+
|
22
|
+
# @!attribute [r] options
|
23
|
+
# @return [Struct]
|
24
|
+
attr_reader :options
|
25
|
+
|
26
|
+
delegate :home, :port, prefix: :solr, to: :solr_config
|
27
|
+
|
28
|
+
alias_method :port, :solr_port
|
29
|
+
|
30
|
+
# @abstract
|
31
|
+
# @return [void]
|
32
|
+
def run
|
33
|
+
raise NotImplementedError, "Must implement #run"
|
34
|
+
end
|
35
|
+
|
36
|
+
# @return [void]
|
37
|
+
def run!(args, options)
|
38
|
+
@args = args
|
39
|
+
@options = options
|
40
|
+
|
41
|
+
options.default shared_options
|
42
|
+
|
43
|
+
solr_config.home = options.solr_home
|
44
|
+
solr_config.port = options.solr_port
|
45
|
+
|
46
|
+
run
|
47
|
+
end
|
48
|
+
|
49
|
+
# @!attribute [r] solr_config
|
50
|
+
# @return [SolrMakr::SolrConfiguration]
|
51
|
+
attr_lazy_reader :solr_config do
|
52
|
+
SolrMakr::SolrConfiguration.new
|
53
|
+
end
|
54
|
+
|
55
|
+
# @!attribute [r] core
|
56
|
+
# @return [SolrMakr::Core]
|
57
|
+
attr_lazy_reader :core do
|
58
|
+
core_name = args[config.core_name_index]
|
59
|
+
|
60
|
+
if core_name.present? || config.acts_on_single_core
|
61
|
+
solr_config.core(name: core_name)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def with_message(message, &block)
|
66
|
+
retval = yield
|
67
|
+
|
68
|
+
if retval != false && options.verbose
|
69
|
+
say message
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
private
|
74
|
+
def shared_options
|
75
|
+
{
|
76
|
+
solr_home: solr_home,
|
77
|
+
solr_port: solr_port
|
78
|
+
}
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
module SolrMakr
|
2
|
+
# @api private
|
3
|
+
class CoreName < Virtus::Attribute
|
4
|
+
NAME_FORMAT = /\A[\w\-]+\z/i
|
5
|
+
|
6
|
+
# @param [String] value
|
7
|
+
# @return [String]
|
8
|
+
def coerce(value)
|
9
|
+
value = value.to_s
|
10
|
+
|
11
|
+
raise 'Invalid Core Name' unless value.present?
|
12
|
+
raise 'Invalid Core Name Format' unless value =~ NAME_FORMAT
|
13
|
+
|
14
|
+
return value
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
class Core
|
19
|
+
include Virtus.model strict: true
|
20
|
+
include SolrRequest
|
21
|
+
|
22
|
+
CONF_PATH = 'conf'
|
23
|
+
|
24
|
+
CONF_FILES = %w[schema.xml solrconfig.xml]
|
25
|
+
|
26
|
+
attribute :name, CoreName
|
27
|
+
attribute :config, SolrConfiguration
|
28
|
+
|
29
|
+
delegate :core_directory, :port, to: :config
|
30
|
+
delegate :exist?, to: :instance_dir
|
31
|
+
|
32
|
+
# @return [Pathname]
|
33
|
+
def instance_dir
|
34
|
+
core_directory.join name
|
35
|
+
end
|
36
|
+
|
37
|
+
def conf_dir
|
38
|
+
instance_dir.join(CONF_PATH)
|
39
|
+
end
|
40
|
+
|
41
|
+
def initialize_configuration!
|
42
|
+
conf_dir.mkpath
|
43
|
+
|
44
|
+
CONF_FILES.each do |conf_file|
|
45
|
+
path = conf_dir.join(conf_file)
|
46
|
+
|
47
|
+
path.write(SolrMakr.template(conf_file))
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
# @return [void]
|
52
|
+
def register_with_solr!
|
53
|
+
params = {
|
54
|
+
action: 'CREATE',
|
55
|
+
name: name,
|
56
|
+
persist: 'true',
|
57
|
+
instanceDir: instance_dir.to_s,
|
58
|
+
loadOnStartup: 'true',
|
59
|
+
config: 'solrconfig.xml',
|
60
|
+
schema: 'schema.xml'
|
61
|
+
}
|
62
|
+
|
63
|
+
solr_request solr_cores_url, params: params
|
64
|
+
end
|
65
|
+
|
66
|
+
def reload!
|
67
|
+
params = {
|
68
|
+
action: 'RELOAD',
|
69
|
+
core: name
|
70
|
+
}
|
71
|
+
|
72
|
+
solr_request solr_cores_url, params: params
|
73
|
+
end
|
74
|
+
|
75
|
+
def unload_from_solr!(purge: false)
|
76
|
+
params = {
|
77
|
+
core: name,
|
78
|
+
action: 'UNLOAD',
|
79
|
+
deleteIndex: 'true',
|
80
|
+
deleteDataDir: 'true'
|
81
|
+
}
|
82
|
+
|
83
|
+
if purge
|
84
|
+
say "Deleting entire instance dir!!!"
|
85
|
+
params[:deleteInstanceDir] = 'true'
|
86
|
+
end
|
87
|
+
|
88
|
+
solr_request solr_cores_url, params: params
|
89
|
+
end
|
90
|
+
|
91
|
+
end
|
92
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
module SolrMakr
|
2
|
+
class CoreStatus
|
3
|
+
include Virtus.model strict: true
|
4
|
+
|
5
|
+
attribute :name, String
|
6
|
+
attribute :start_time, Time
|
7
|
+
attribute :uptime, Integer, default: 0
|
8
|
+
attribute :default_core, Boolean, default: false
|
9
|
+
attribute :size, String, default: '0 bytes'
|
10
|
+
attribute :size_in_bytes, Integer, default: 0
|
11
|
+
attribute :documents, Integer, default: 0
|
12
|
+
|
13
|
+
class << self
|
14
|
+
LOOKS_TRUE = /\At(?:rue)?\z/i
|
15
|
+
|
16
|
+
NAME_XML = 'str[name="name"]'
|
17
|
+
DEFAULT_CORE_XML = 'bool[name="isDefaultCore"]'
|
18
|
+
SIZE_XML = 'str[name="size"]'
|
19
|
+
SIZE_IN_BYTES_XML = 'long[name="sizeInBytes"]'
|
20
|
+
START_TIME_XML = 'date[name="startTime"]'
|
21
|
+
UPTIME_XML = 'long[name="uptime"]'
|
22
|
+
DOCUMENTS_XML = 'int[name="numDocs"]'
|
23
|
+
|
24
|
+
# @return [SolrMakr::CoreStatus]
|
25
|
+
def from_xml(node)
|
26
|
+
params = {
|
27
|
+
name: text_at_css(node, NAME_XML),
|
28
|
+
size: text_at_css(node, SIZE_XML),
|
29
|
+
size_in_bytes: text_at_css(node, SIZE_IN_BYTES_XML),
|
30
|
+
documents: text_at_css(node, DOCUMENTS_XML),
|
31
|
+
uptime: text_at_css(node, UPTIME_XML)
|
32
|
+
}
|
33
|
+
|
34
|
+
params[:default_core] = text_at_css(node, DEFAULT_CORE_XML) do |res|
|
35
|
+
res.to_s.match(LOOKS_TRUE).present?
|
36
|
+
end
|
37
|
+
|
38
|
+
params[:start_time] = text_at_css(node, START_TIME_XML) do |res|
|
39
|
+
Time.parse res if res.present?
|
40
|
+
end
|
41
|
+
|
42
|
+
new params
|
43
|
+
end
|
44
|
+
|
45
|
+
def text_at_css(node, selector, &block)
|
46
|
+
result = node.at_css(selector).try(:text)
|
47
|
+
|
48
|
+
if block_given?
|
49
|
+
yield result
|
50
|
+
else
|
51
|
+
result
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
module SolrMakr
|
2
|
+
# @api private
|
3
|
+
class Pathlike < Virtus::Attribute
|
4
|
+
# @param [String / Pathname] value
|
5
|
+
# @return [Pathname]
|
6
|
+
def coerce(value)
|
7
|
+
Pathname.new value
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
class SolrConfiguration
|
12
|
+
include Virtus.model strict: true
|
13
|
+
include SolrRequest
|
14
|
+
|
15
|
+
DEFAULT_HOME = '/opt/solr/jetty-solr/'
|
16
|
+
DEFAULT_PORT = 8983
|
17
|
+
|
18
|
+
attribute :home, Pathlike, default: :default_home
|
19
|
+
attribute :port, Integer, default: :default_port
|
20
|
+
|
21
|
+
def initialize(env = ENV)
|
22
|
+
@_env = env
|
23
|
+
|
24
|
+
super()
|
25
|
+
end
|
26
|
+
|
27
|
+
def env
|
28
|
+
@_env ||= ENV
|
29
|
+
end
|
30
|
+
|
31
|
+
def core(name: nil)
|
32
|
+
Core.new name: name, config: self
|
33
|
+
end
|
34
|
+
|
35
|
+
def cores
|
36
|
+
core_directory.children.each_with_object [] do |path, cores|
|
37
|
+
cores << core(name: path.basename) if looks_like_core_directory?(path)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# @return [String]
|
42
|
+
def core_directory
|
43
|
+
home.join 'solr'
|
44
|
+
end
|
45
|
+
|
46
|
+
# @return [<SolrMakr::CoreStatus>]
|
47
|
+
def solr_status
|
48
|
+
resp = solr_request solr_cores_url, params: { action: 'STATUS' }
|
49
|
+
|
50
|
+
parsed = Nokogiri::XML(resp.body)
|
51
|
+
|
52
|
+
statuses = parsed.at_css('lst[name="status"]').try(:children)
|
53
|
+
|
54
|
+
return [] unless statuses.present?
|
55
|
+
|
56
|
+
statuses.each_with_object [] do |status, list|
|
57
|
+
created = SolrMakr::CoreStatus.from_xml status# rescue nil
|
58
|
+
|
59
|
+
list << created if created.present?
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
private
|
64
|
+
def looks_like_core_directory?(path)
|
65
|
+
path.join('data').exist?
|
66
|
+
end
|
67
|
+
|
68
|
+
def default_port
|
69
|
+
Integer(env.fetch 'SOLR_PORT', DEFAULT_PORT)
|
70
|
+
rescue ArgumentError
|
71
|
+
DEFAULT_PORT
|
72
|
+
end
|
73
|
+
|
74
|
+
def default_home
|
75
|
+
Pathname.new env.fetch 'SOLR_HOME', DEFAULT_HOME
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|