solr_makr 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|