solr_cloud-connection 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.env.local +3 -0
- data/.rspec +3 -0
- data/.standard.yml +3 -0
- data/CHANGELOG.md +5 -0
- data/Dockerfile +30 -0
- data/LICENSE.txt +9 -0
- data/README.md +171 -0
- data/Rakefile +10 -0
- data/compose.yml +34 -0
- data/env.development +4 -0
- data/lib/solr_cloud/alias.rb +67 -0
- data/lib/solr_cloud/collection.rb +125 -0
- data/lib/solr_cloud/configset.rb +35 -0
- data/lib/solr_cloud/connection/alias_admin.rb +85 -0
- data/lib/solr_cloud/connection/collection_admin.rb +81 -0
- data/lib/solr_cloud/connection/configset_admin.rb +119 -0
- data/lib/solr_cloud/connection/version.rb +7 -0
- data/lib/solr_cloud/connection.rb +158 -0
- data/lib/solr_cloud/errors.rb +22 -0
- metadata +163 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 1989962c4834b362be714c08199791950034638d4e2ea26bfa1368e377390f3b
|
4
|
+
data.tar.gz: ed3621358d19671db37394b04b0df83e700b2dbd7b45dc7cd3e545c782ed64c6
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 6161fb600d516c459a843f9f8054cf2b3db2cb0eadf04678af037b7bc55e86b828b08e167f01391dbb9b7b2d4c5a5759851fc322a71a8d545651734e44df978c
|
7
|
+
data.tar.gz: 7082a625e5ffa7f04e17638db3782b0ac3715a3955413539543e39297cb589825cde87abd06dc2aff1cde7c59581da25a4d6fb372c7f3b177d780e035a5d1bdd
|
data/.env.local
ADDED
data/.rspec
ADDED
data/.standard.yml
ADDED
data/CHANGELOG.md
ADDED
data/Dockerfile
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
FROM ruby:3.2 AS development
|
2
|
+
|
3
|
+
# Check https://rubygems.org/gems/bundler/versions for the latest version.
|
4
|
+
ARG UNAME=app
|
5
|
+
ARG UID=1000
|
6
|
+
ARG GID=1000
|
7
|
+
|
8
|
+
## Install Vim (optional)
|
9
|
+
RUN apt-get update -yqq && apt-get install -yqq --no-install-recommends \
|
10
|
+
vim-tiny
|
11
|
+
|
12
|
+
RUN gem install bundler
|
13
|
+
|
14
|
+
RUN groupadd -g ${GID} -o ${UNAME}
|
15
|
+
RUN useradd -m -d /app -u ${UID} -g ${GID} -o -s /bin/bash ${UNAME}
|
16
|
+
RUN mkdir -p /gems && chown ${UID}:${GID} /gems
|
17
|
+
|
18
|
+
ENV PATH="$PATH:/app/exe:/app/bin"
|
19
|
+
USER $UNAME
|
20
|
+
|
21
|
+
ENV BUNDLE_PATH /gems
|
22
|
+
|
23
|
+
WORKDIR /app
|
24
|
+
|
25
|
+
FROM development AS production
|
26
|
+
|
27
|
+
COPY --chown=${UID}:${GID} . /app
|
28
|
+
|
29
|
+
RUN bundle install
|
30
|
+
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,9 @@
|
|
1
|
+
Copyright (c) 2022, Regents of the University of Michigan. All rights reserved.
|
2
|
+
|
3
|
+
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
4
|
+
|
5
|
+
Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
6
|
+
Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
7
|
+
Neither the name of the University of Michigan nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
|
8
|
+
|
9
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OF THE UNIVERSITY OF MICHIGAN BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
data/README.md
ADDED
@@ -0,0 +1,171 @@
|
|
1
|
+
# SolrCloud::Connection
|
2
|
+
|
3
|
+
Do basic administrative tasks on a running Solr cloud instance, including:
|
4
|
+
|
5
|
+
* list, create, and delete configsets, collections, and aliases
|
6
|
+
* get basic version information for the running solr
|
7
|
+
* check on the health of individual collections
|
8
|
+
* treat an alias (mostly) as a collection, just as you'd expect
|
9
|
+
* TODO automatically generate methods to talk to defined requestHandlers
|
10
|
+
* TODO collect and deal with search results in a sane way
|
11
|
+
|
12
|
+
## Caveats
|
13
|
+
|
14
|
+
* At this point the API is unstable, and it doesn't do any actual, you know, searching.
|
15
|
+
* Due to there not being any sense of an atomic action when administering solr, this gem does
|
16
|
+
_no caching_. This means the individual actions can involve several round-trips to solr. On the flip
|
17
|
+
side, if you're doing so much admin that it's a bottleneck, you're well outside this gem's target case.
|
18
|
+
* While solr aliases can point to more than one collection at a time, this gem enforces one collection
|
19
|
+
per alias (although many aliases can point to the same collection)
|
20
|
+
|
21
|
+
## Usage
|
22
|
+
|
23
|
+
### Create a connection to a running solr
|
24
|
+
|
25
|
+
The connection object is the basis of all the other stuff. Everything will be created, directly
|
26
|
+
or indirectly, through the connection. While using the collection/alias/configset objects
|
27
|
+
is preferred, the connection on its own can do most anything. See SolrCloud::Connection for
|
28
|
+
the docs.
|
29
|
+
|
30
|
+
A simple connection is made if you pass in basic info, or you can create a faraday connection
|
31
|
+
and pass it in yourself.
|
32
|
+
|
33
|
+
```ruby
|
34
|
+
|
35
|
+
require "solr_cloud/connection"
|
36
|
+
|
37
|
+
solr = SolrCloud::connect.new(url: "http://localhost:9999", username: "user", password: "password")
|
38
|
+
# #=> <SolrCloud::Connection http://localhost:9999/>
|
39
|
+
|
40
|
+
# or bring your own Faraday object
|
41
|
+
solr = SolrCloud::connect.new_with_faraday(faraday_connection)
|
42
|
+
|
43
|
+
```
|
44
|
+
|
45
|
+
### Configsets
|
46
|
+
|
47
|
+
Configuration sets can be created by giving a path to the `conf` directory (with
|
48
|
+
`solrconfig.xml` and the schema and such) and a name.
|
49
|
+
|
50
|
+
```ruby
|
51
|
+
connect.configset_names #=> []
|
52
|
+
cset = connect.create_configset(name: "myconfig", confdir: "/path/to/yourconfig/conf")
|
53
|
+
|
54
|
+
# Get a list of existing configsets
|
55
|
+
arr_of_configsets_objects = @connect.configsets
|
56
|
+
arr_of_names_as_strings = @connect.configset_names
|
57
|
+
|
58
|
+
# Test and see if it exists by name
|
59
|
+
connect.configset?("myconfig") #=> true
|
60
|
+
|
61
|
+
# If it already exists, you can just grab it by name
|
62
|
+
|
63
|
+
def_config = connect.configset("_default")
|
64
|
+
|
65
|
+
# It makes sure you don't overwrite when creating a new set
|
66
|
+
connect.create_configset(name: "myconfig", confdir: "/path/to/yourconfig/conf")
|
67
|
+
#=> WontOverwriteError
|
68
|
+
|
69
|
+
# ...but you can force it
|
70
|
+
connect.create_configset(name: "myconfig", confdir: "/path/to/yourconfig/conf", force: true)
|
71
|
+
|
72
|
+
# And get rid of it
|
73
|
+
myconfig.delete!
|
74
|
+
connect.configset?("myconfig") #=> false
|
75
|
+
|
76
|
+
```
|
77
|
+
|
78
|
+
### Collections
|
79
|
+
|
80
|
+
Collections can be listed, tested for health and aliases, used to create an alias, and deleted.
|
81
|
+
|
82
|
+
```ruby
|
83
|
+
|
84
|
+
connect.collection_names #=> []
|
85
|
+
connect.create_collection(name: "mycoll", configset: "does_not_exist") #=> SolrCloud::NoSuchConfigSetError: Configset does_not_exist doesn't exist
|
86
|
+
mycoll = connect.create_collection(name: "mycoll", configset: "_default")
|
87
|
+
mycoll.name #=> "mycoll"
|
88
|
+
|
89
|
+
# Test and see if it exists
|
90
|
+
connect.collection?("mycoll") #=> true
|
91
|
+
|
92
|
+
# Get all of them
|
93
|
+
|
94
|
+
arr_of_collection_objects = connect.collections
|
95
|
+
arr_of_names_as_strings = connect.collection_names
|
96
|
+
|
97
|
+
# or get a single one by name
|
98
|
+
coll = connect.collection("some_other_collection")
|
99
|
+
|
100
|
+
mycoll.alive? # => true
|
101
|
+
mycoll.healthy? #=> true. I'm not sure how these are different
|
102
|
+
|
103
|
+
mycoll.alias? # false. It's a collection.
|
104
|
+
|
105
|
+
# Which configset is it based on?
|
106
|
+
mycoll.configset #=> <SolrCloud::Configset '_default' at http://localhost:9999/>
|
107
|
+
|
108
|
+
# Sniff out and create aliases
|
109
|
+
mycoll.aliases #=> [] None as of yet
|
110
|
+
myalias = mycoll.alias_as("myalias")
|
111
|
+
|
112
|
+
mycoll.aliases #=> [<SolrCloud::Alias 'myalias' (alias of 'mycoll')>]
|
113
|
+
mycoll.alias_names #=> ["myalias"]
|
114
|
+
|
115
|
+
# Collection, Alias, and Configset can all access the underlying connection object
|
116
|
+
# and call `get`, `post`, `put`, and `delete`
|
117
|
+
mycoll.collection #=>
|
118
|
+
mycoll.collection.get("path/from/connection/url", arg1: "One", arg2: "Two")
|
119
|
+
|
120
|
+
# Try to delete the collection
|
121
|
+
mycoll.delete! #=> SolrCloud::CollectionAliasedError: Collection 'mycoll' can't be deleted; it's in use by aliases ["myalias"]
|
122
|
+
myalias.delete!
|
123
|
+
|
124
|
+
mycoll.delete!
|
125
|
+
|
126
|
+
```
|
127
|
+
|
128
|
+
### Aliases
|
129
|
+
|
130
|
+
In all the important ways, aliases can be treated like collections. Here are the exceptions.
|
131
|
+
|
132
|
+
```ruby
|
133
|
+
|
134
|
+
# You can create an alias from the collection you're aliasing
|
135
|
+
myalias = mycoll.alias_as("myalias")
|
136
|
+
|
137
|
+
# Ask the connection object if it exists, and get it
|
138
|
+
connect.alias?("myalias") #=> true
|
139
|
+
myalias = connect.alias("myalias")
|
140
|
+
|
141
|
+
# As this object if it's an alias, as opposed to a collection
|
142
|
+
myalias.alias? #=> true
|
143
|
+
|
144
|
+
# Set the collection this alias points to, removing its link from its existing collection
|
145
|
+
myalias.collection = my_other_collection #=> sets myalias to point at my_other_collection
|
146
|
+
|
147
|
+
myalias.delete!
|
148
|
+
```
|
149
|
+
|
150
|
+
## Installation
|
151
|
+
|
152
|
+
Install the gem and add to the application's Gemfile by executing:
|
153
|
+
|
154
|
+
$ bundle add solr_cloud-connection
|
155
|
+
|
156
|
+
If bundler is not being used to manage dependencies, install the gem by executing:
|
157
|
+
|
158
|
+
$ gem install solr_cloud-connection
|
159
|
+
|
160
|
+
## Testing
|
161
|
+
|
162
|
+
This repository is set up to run tests under docker.
|
163
|
+
|
164
|
+
1. docker compose build
|
165
|
+
2. docker compose run app bundle install
|
166
|
+
3. docker compose up
|
167
|
+
4. docker compose run app bundle exec rspec
|
168
|
+
|
169
|
+
## Contributing
|
170
|
+
|
171
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/mlibrary/solr_cloud-connect.
|
data/Rakefile
ADDED
data/compose.yml
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
services:
|
2
|
+
app:
|
3
|
+
build:
|
4
|
+
context: .
|
5
|
+
target: development
|
6
|
+
platform: linux/amd64
|
7
|
+
volumes:
|
8
|
+
- .:/app
|
9
|
+
- gem_cache:/gems
|
10
|
+
env_file:
|
11
|
+
- .env
|
12
|
+
- env.development
|
13
|
+
command: "tail -f /dev/null"
|
14
|
+
|
15
|
+
solr:
|
16
|
+
build: spec/data/solr_docker/.
|
17
|
+
ports:
|
18
|
+
- "9999:8983"
|
19
|
+
environment:
|
20
|
+
- ZK_HOST=zoo:2181
|
21
|
+
depends_on:
|
22
|
+
- zoo
|
23
|
+
command: solr-foreground
|
24
|
+
|
25
|
+
zoo:
|
26
|
+
image: zookeeper
|
27
|
+
ports:
|
28
|
+
- 2999:2181
|
29
|
+
environment:
|
30
|
+
ZOO_MY_ID: 1
|
31
|
+
ZOO_SERVERS: server.1=0.0.0.0:2888:3888;2181
|
32
|
+
|
33
|
+
volumes:
|
34
|
+
gem_cache:
|
data/env.development
ADDED
@@ -0,0 +1,67 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SolrCloud
|
4
|
+
# An alias can mostly be just treated as a collection. It will identify itself as an alias if you
|
5
|
+
# call #alias, and it can return and change the underlying collection it points to.
|
6
|
+
|
7
|
+
# An alias shouldn't be created directly. Rather, get an existing one with
|
8
|
+
# Connection#alias, or from a collection, or create one with
|
9
|
+
# Collection#alias_as
|
10
|
+
class Alias < Collection
|
11
|
+
# An alias is, shockingly, an alias. Convenience to differentiate aliases from collections.
|
12
|
+
# @see SolrCloud::Connection#alias?
|
13
|
+
def alias?
|
14
|
+
true
|
15
|
+
end
|
16
|
+
|
17
|
+
# Delete this alias
|
18
|
+
# @return [SolrCloud::Connection]
|
19
|
+
def delete!
|
20
|
+
coll = collection
|
21
|
+
connection.delete_alias(name)
|
22
|
+
coll
|
23
|
+
end
|
24
|
+
|
25
|
+
# Get the collection this alias points to.
|
26
|
+
# In real life, Solr will allow an alias to point to more than one collection. Functionality
|
27
|
+
# for this might be added at some point
|
28
|
+
# @return [SolrCloud::Collection]
|
29
|
+
def collection
|
30
|
+
connection.collection_for_alias(name)
|
31
|
+
end
|
32
|
+
|
33
|
+
# Redefine what collection this alias points to
|
34
|
+
# This is equivalent to dropping/re-adding the alias, or calling connection.create_alias with `force: true`
|
35
|
+
# @param coll [String, Collection] either the name of the collection, or a collection object itself
|
36
|
+
# @return [Collection] the now-current collection
|
37
|
+
def collection=(coll)
|
38
|
+
collect_name = case coll
|
39
|
+
when String
|
40
|
+
coll
|
41
|
+
when Collection
|
42
|
+
coll.name
|
43
|
+
else
|
44
|
+
raise "Alias#collection= only takes a name(string) or a collection, not '#{coll}'"
|
45
|
+
end
|
46
|
+
raise NoSuchCollectionError unless connection.collection?(collect_name)
|
47
|
+
connection.create_alias(name: name, collection_name: collect_name, force: true)
|
48
|
+
end
|
49
|
+
|
50
|
+
# Get basic information on the underlying collection, so inherited methods that
|
51
|
+
# use it (e.g., #healthy?) will work.
|
52
|
+
# @overload info()
|
53
|
+
def info
|
54
|
+
collection.info
|
55
|
+
end
|
56
|
+
|
57
|
+
def inspect
|
58
|
+
"<#{self.class} '#{name}' (alias of '#{collection.name}')>"
|
59
|
+
end
|
60
|
+
|
61
|
+
alias_method :to_s, :inspect
|
62
|
+
|
63
|
+
def pretty_print(q)
|
64
|
+
q.text inspect
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,125 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "solr_cloud/connection"
|
4
|
+
|
5
|
+
module SolrCloud
|
6
|
+
# A Collection provides basic services on the collection -- checking its health,
|
7
|
+
# creating or reporting aliases, and deleting itself.
|
8
|
+
class Collection
|
9
|
+
attr_reader :name, :connection
|
10
|
+
|
11
|
+
# In general, users shouldn't use Collection.new; instead, use
|
12
|
+
# connection.create_collection(name: "coll_name", configset: "existing_configset_name")
|
13
|
+
#
|
14
|
+
# @param [String] name The name of the (already existing) collection
|
15
|
+
# @param [SolrCloud::Connection] connection Connection to the solr "root" (http://blah:8888/)
|
16
|
+
def initialize(name:, connection:)
|
17
|
+
# raise NoSuchCollectionError.new("No collection #{name}") unless connection.collection?(name)
|
18
|
+
@connection = connection.dup
|
19
|
+
@name = name
|
20
|
+
@sp = "/solr/#{name}"
|
21
|
+
end
|
22
|
+
|
23
|
+
# Delete this collection. Unlike the #delete_collection call on a Connection object,
|
24
|
+
# for this one we throw an error if the collection isn't found, since that means
|
25
|
+
# it was deleted via some other mechanism after this object was created and should probably be investigated.
|
26
|
+
# @return [Connection] The underlying SolrCloud::Connection
|
27
|
+
def delete!
|
28
|
+
raise NoSuchCollectionError unless exist?
|
29
|
+
connection.delete_collection(name)
|
30
|
+
connection
|
31
|
+
end
|
32
|
+
|
33
|
+
# Check to see if the collection is alive
|
34
|
+
# @return [Boolean]
|
35
|
+
def alive?
|
36
|
+
connection.get("solr/#{name}/admin/ping").body["status"]
|
37
|
+
rescue Faraday::ResourceNotFound
|
38
|
+
false
|
39
|
+
end
|
40
|
+
|
41
|
+
alias_method :exist?, :alive?
|
42
|
+
|
43
|
+
# Is this an alias?
|
44
|
+
# Putting this in here breaks all sorts of isolation principles,
|
45
|
+
# but being able to call #alias? on anything collection-like is
|
46
|
+
# convenient
|
47
|
+
def alias?
|
48
|
+
false
|
49
|
+
end
|
50
|
+
|
51
|
+
# Access to the root info from the api. Mostly for internal use, but not labeled as such
|
52
|
+
# 'cause users will almost certainly find a use for it.
|
53
|
+
def info
|
54
|
+
connection.get("api/collections/#{name}").body["cluster"]["collections"][name]
|
55
|
+
end
|
56
|
+
|
57
|
+
# Reported as healthy?
|
58
|
+
# @return [Boolean]
|
59
|
+
def healthy?
|
60
|
+
info["health"] == "GREEN"
|
61
|
+
end
|
62
|
+
|
63
|
+
# A (possibly empty) list of aliases targeting this collection
|
64
|
+
# @return [Array<SolrCloud::Alias>] list of aliases that point to this collection
|
65
|
+
def aliases
|
66
|
+
connection.raw_alias_map.select { |a, c| c == name }.keys.map { |aname| Alias.new(name: aname, connection: connection) }
|
67
|
+
end
|
68
|
+
|
69
|
+
# The names of the aliases that point to this collection
|
70
|
+
# @return [Array<String>] the alias names
|
71
|
+
def alias_names
|
72
|
+
aliases.map(&:name)
|
73
|
+
end
|
74
|
+
|
75
|
+
# Get a specific alias by name
|
76
|
+
# @param aname [String] name of the alias
|
77
|
+
def alias(aname)
|
78
|
+
aliases.find {|a| a.name == aname} || (raise NoSuchAliasError.new("No alias named '#{aname}' pointing to collection #{name}"))
|
79
|
+
end
|
80
|
+
|
81
|
+
# Create an alias for this collection. Always forces an overwrite unless you tell it not to
|
82
|
+
# @param alias_name [String] name of the alias to create
|
83
|
+
# @param force [Boolean] whether or not to overwrite an existing alias
|
84
|
+
# @return [SolrCloud::Alias]
|
85
|
+
def alias_as(alias_name, force: true)
|
86
|
+
connection.create_alias(name: alias_name, collection_name: name, force: true)
|
87
|
+
end
|
88
|
+
|
89
|
+
alias_method :alias_to, :alias_as
|
90
|
+
alias_method :create_alias, :alias_as
|
91
|
+
|
92
|
+
# Send a commit (soft if unspecified)
|
93
|
+
# @return self
|
94
|
+
def commit(hard: false)
|
95
|
+
if hard
|
96
|
+
connection.get("solr/#{name}/update", commit: true)
|
97
|
+
else
|
98
|
+
connection.get("solr/#{name}/update", softCommit: true)
|
99
|
+
end
|
100
|
+
self
|
101
|
+
end
|
102
|
+
|
103
|
+
# What configset was this created with?
|
104
|
+
# @return [SolrCloud::ConfigSet]
|
105
|
+
def configset
|
106
|
+
Configset.new(name: info["configName"], connection: connection)
|
107
|
+
end
|
108
|
+
|
109
|
+
def inspect
|
110
|
+
anames = alias_names
|
111
|
+
astring = if anames.empty?
|
112
|
+
""
|
113
|
+
else
|
114
|
+
" (aliased by #{anames.map { |x| "'#{x}'" }.join(", ")})"
|
115
|
+
end
|
116
|
+
"<#{self.class} '#{name}'#{astring}>"
|
117
|
+
end
|
118
|
+
|
119
|
+
alias_method :to_s, :inspect
|
120
|
+
|
121
|
+
def pretty_print(q)
|
122
|
+
q.text inspect
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "solr_cloud/connection"
|
4
|
+
|
5
|
+
module SolrCloud
|
6
|
+
# A configset can't do much by itself, other than try to delete itself and
|
7
|
+
# throw an error if that's an illegal operation (because a collection is
|
8
|
+
# using it)
|
9
|
+
class Configset
|
10
|
+
attr_reader :name, :connection
|
11
|
+
|
12
|
+
def initialize(name:, connection:)
|
13
|
+
@name = name
|
14
|
+
@connection = connection
|
15
|
+
end
|
16
|
+
|
17
|
+
# Delete this configset.
|
18
|
+
# @see SolrCloud::Connection#delete_configset
|
19
|
+
# @return The underlying connection
|
20
|
+
def delete!
|
21
|
+
@connection.delete_configset(name)
|
22
|
+
@connection
|
23
|
+
end
|
24
|
+
|
25
|
+
def inspect
|
26
|
+
"<#{self.class.name} '#{name}' at #{connection.url}>"
|
27
|
+
end
|
28
|
+
|
29
|
+
alias_method :to_s, :inspect
|
30
|
+
|
31
|
+
def pretty_print(q)
|
32
|
+
q.text inspect
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SolrCloud
|
4
|
+
class Connection
|
5
|
+
# methods having to do with aliases, to be included by the connection object.
|
6
|
+
# These are split out only to make it easier to deal with them.
|
7
|
+
module AliasAdmin
|
8
|
+
AliasCollectionPair = Struct.new(:alias, :collection)
|
9
|
+
|
10
|
+
# Create an alias for the given collection name
|
11
|
+
# @todo allow an alias to point to more than one collection?
|
12
|
+
# @param name [String] Name of the new alias
|
13
|
+
# @param collection_name [String] name of the collection
|
14
|
+
# @param force [Boolean] whether to overwrite an existing alias
|
15
|
+
# @raise [WontOverwriteError] if the alias exists and force isn't true
|
16
|
+
# @raise [NoSuchCollectionError] if the collections isn't found
|
17
|
+
# @return [Alias] the newly-created alias
|
18
|
+
def create_alias(name:, collection_name:, force: false)
|
19
|
+
raise NoSuchCollectionError.new("Can't find collection #{collection_name}") unless collection?(collection_name)
|
20
|
+
if alias?(name) && !force
|
21
|
+
raise WontOverwriteError.new("Alias '#{name}' already points to collection '#{self.alias(name).collection.name}'; won't overwrite without force: true")
|
22
|
+
end
|
23
|
+
connection.get("solr/admin/collections", action: "CREATEALIAS", name: name, collections: collection_name)
|
24
|
+
SolrCloud::Alias.new(name: name, connection: self)
|
25
|
+
end
|
26
|
+
|
27
|
+
# Is there an alias with this name?
|
28
|
+
# @return [Boolean]
|
29
|
+
def alias?(name)
|
30
|
+
alias_names.include? name
|
31
|
+
end
|
32
|
+
|
33
|
+
# Delete the alias
|
34
|
+
# @param name [String] Name of the alias to delete
|
35
|
+
# @return [SolrCloud::Connection]
|
36
|
+
def delete_alias(name)
|
37
|
+
connection.get("solr/admin/collections", action: "DELETEALIAS", name: name)
|
38
|
+
end
|
39
|
+
|
40
|
+
# The "raw" alias map, which just maps alias names to collection names
|
41
|
+
# @return [Hash<String, String>]
|
42
|
+
def raw_alias_map
|
43
|
+
connection.get("solr/admin/collections", action: "LISTALIASES").body["aliases"]
|
44
|
+
end
|
45
|
+
|
46
|
+
# Get the aliases and create a map of the form
|
47
|
+
# @return [Hash<String,Alias>] A hash mapping alias names to alias objects
|
48
|
+
def alias_map
|
49
|
+
raw_alias_map.keys.each_with_object({}) do |alias_name, h|
|
50
|
+
h[alias_name] = SolrCloud::Alias.new(name: alias_name, connection: self)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
# List of alias objects
|
55
|
+
# @return [Array<SolrCloud::Alias>] List of aliases
|
56
|
+
def aliases
|
57
|
+
alias_map.values
|
58
|
+
end
|
59
|
+
|
60
|
+
# List of alias names
|
61
|
+
# @return [Array<String>] the alias names
|
62
|
+
def alias_names
|
63
|
+
alias_map.keys
|
64
|
+
end
|
65
|
+
|
66
|
+
# Get an alias object for the given name
|
67
|
+
# @param name [String] the name of the existing alias
|
68
|
+
# @raise [SolrCloud::NoSuchAliasError] if it doesn't exist
|
69
|
+
# @return [SolrCloud::Alias]
|
70
|
+
def alias(name)
|
71
|
+
am = alias_map
|
72
|
+
raise NoSuchAliasError unless am[name]
|
73
|
+
am[name]
|
74
|
+
end
|
75
|
+
|
76
|
+
# Get the collection associated with an alias
|
77
|
+
# @param name [String] alias name
|
78
|
+
# @return [Collection] collection associated with the alias
|
79
|
+
def collection_for_alias(name)
|
80
|
+
collname = connection.get("solr/admin/collections", action: "LISTALIASES").body["aliases"][name]
|
81
|
+
Collection.new(name: collname, connection: self)
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SolrCloud
|
4
|
+
class Connection
|
5
|
+
# methods having to do with connections, to be included by the connection object.
|
6
|
+
# These are split out only to make it easier to deal with them.
|
7
|
+
module CollectionAdmin
|
8
|
+
# Create a new collection
|
9
|
+
# @param name [String] Name for the new collection
|
10
|
+
# @param configset [String] name of the configset to use for this collection
|
11
|
+
# @param version [String] A "version" which will be appended to the name following an underscore, if given.
|
12
|
+
# Useful for testing and cronjobs.
|
13
|
+
# @param shards [Integer]
|
14
|
+
# @param replication_factor [Integer]
|
15
|
+
# @todo Let version take symbols like :date and :datetime
|
16
|
+
# @raise [NoSuchConfigSetError] if the named configset doesn't exist
|
17
|
+
# @raise [WontOverwriteError] if the collection already exists
|
18
|
+
# @return [Collection] the collection created
|
19
|
+
def create_collection(name:, configset:, version: nil, shards: 1, replication_factor: 1)
|
20
|
+
fullname = if version
|
21
|
+
"#{name}_#{version}"
|
22
|
+
else
|
23
|
+
name
|
24
|
+
end
|
25
|
+
|
26
|
+
raise WontOverwriteError.new("Collection #{fullname} already exists") if collection?(fullname)
|
27
|
+
raise NoSuchConfigSetError.new("Configset '#{configset}' doesn't exist") unless configset?(configset)
|
28
|
+
|
29
|
+
args = {
|
30
|
+
:action => "CREATE",
|
31
|
+
:name => fullname,
|
32
|
+
:numShards => shards,
|
33
|
+
:replicationFactor => replication_factor,
|
34
|
+
"collection.configName" => configset
|
35
|
+
}
|
36
|
+
connection.get("solr/admin/collections", args)
|
37
|
+
collection(name)
|
38
|
+
end
|
39
|
+
|
40
|
+
# Get a list of existing collections
|
41
|
+
# @return [Array<SolrCloud::Collection>] possibly empty list of collection objects
|
42
|
+
def collections
|
43
|
+
collection_names.map { |coll| collection(coll) }
|
44
|
+
end
|
45
|
+
|
46
|
+
# A list of the names of existing collections
|
47
|
+
# @return [Array<String>] the collection names, or empty array if there are none
|
48
|
+
def collection_names
|
49
|
+
connection.get("api/collections").body["collections"]
|
50
|
+
end
|
51
|
+
|
52
|
+
# @param name [String] name of the collection to check on
|
53
|
+
# @return [Boolean] Whether a collection with the passed name exists
|
54
|
+
def collection?(name)
|
55
|
+
collection_names.include? name
|
56
|
+
end
|
57
|
+
|
58
|
+
# Remove the configuration set with the given name. No-op if the
|
59
|
+
# collection doesn't actually exist. Use #connection? manually if you need to raise on does-not-exist
|
60
|
+
# @param [String,Symbol] name The name of the configuration set
|
61
|
+
# @return [Connection] self
|
62
|
+
def delete_collection(name)
|
63
|
+
if collection? name
|
64
|
+
connection.get("solr/admin/collections", {action: "DELETE", name: name})
|
65
|
+
end
|
66
|
+
self
|
67
|
+
rescue Faraday::BadRequestError
|
68
|
+
raise SolrCloud::CollectionAliasedError.new("Collection '#{name}' can't be deleted; it's in use by aliases #{collection(name).alias_names}")
|
69
|
+
end
|
70
|
+
|
71
|
+
# Get a connection object specifically for the named collection
|
72
|
+
# @param collection_name [String] name of the (already existing) collection
|
73
|
+
# @return [SolrCloud::Connection::Collection] The collection connection
|
74
|
+
# @raise [NoSuchCollectionError] if the collection doesn't exist
|
75
|
+
def collection(collection_name)
|
76
|
+
raise NoSuchCollectionError.new("Collection '#{collection_name}' not found") unless collection?(collection_name)
|
77
|
+
Collection.new(name: collection_name, connection: self)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,119 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "zip"
|
4
|
+
|
5
|
+
module SolrCloud
|
6
|
+
class Connection
|
7
|
+
# methods having to do with configsets, to be included by the connection object.
|
8
|
+
# These are split out only to make it easier to deal with them.
|
9
|
+
module ConfigsetAdmin
|
10
|
+
# Get a list of the already-defined configSets
|
11
|
+
# @return [Array<Configset>] possibly empty list of configSets
|
12
|
+
def configsets
|
13
|
+
configset_names.map { |cs| Configset.new(name: cs, connection: self) }
|
14
|
+
end
|
15
|
+
|
16
|
+
# @return [Array<String>] the names of the config sets
|
17
|
+
def configset_names
|
18
|
+
connection.get("api/cluster/configs").body["configSets"]
|
19
|
+
end
|
20
|
+
|
21
|
+
alias_method :configurations, :configsets
|
22
|
+
|
23
|
+
# Check to see if a configset is defined
|
24
|
+
# @param name [String] Name of the configSet
|
25
|
+
# @return [Boolean] Whether a configset with that name exists
|
26
|
+
def configset?(name)
|
27
|
+
configset_names.include? name.to_s
|
28
|
+
end
|
29
|
+
|
30
|
+
# Given the path to a solr configuration "conf" directory (i.e., the one with
|
31
|
+
# solrconfig.xml in it), zip it up and send it to solr as a new configset.
|
32
|
+
# @param name [String] Name to give the new configset
|
33
|
+
# @param confdir [String, Pathname] Path to the solr configuration "conf" directory
|
34
|
+
# @param force [Boolean] Whether or not to overwrite an existing configset if there is one
|
35
|
+
# @param version [String] A "version" which will be appended to the name if given. Useful for
|
36
|
+
# testing and cronjobs.
|
37
|
+
# @raise [WontOverwriteError] if the configset already exists and "force" is false
|
38
|
+
# @return [String] the name of the configset created
|
39
|
+
def create_configset(name:, confdir:, force: false, version: "")
|
40
|
+
config_set_name = name + version.to_s
|
41
|
+
if configset?(config_set_name) && !force
|
42
|
+
raise WontOverwriteError.new("Won't replace configset #{config_set_name} unless 'force: true' passed ")
|
43
|
+
end
|
44
|
+
zfile = "#{Dir.tmpdir}/solr_add_configset_#{name}_#{Time.now.hash}.zip"
|
45
|
+
z = ZipFileGenerator.new(confdir, zfile)
|
46
|
+
z.write
|
47
|
+
@raw_connection.put("api/cluster/configs/#{config_set_name}") do |req|
|
48
|
+
req.body = File.binread(zfile)
|
49
|
+
end
|
50
|
+
# TODO: Error check in here somewhere
|
51
|
+
FileUtils.rm(zfile, force: true)
|
52
|
+
config_set_name
|
53
|
+
end
|
54
|
+
|
55
|
+
# Remove the configuration set with the given name. No-op if the
|
56
|
+
# configset doesn't actually exist. Use #configset? manually if you need to raise on does-not-exist
|
57
|
+
# @param [String,Symbol] name The name of the configuration set
|
58
|
+
# @raise [InUseError] if the configset can't be deleted because it's in use by a live collection
|
59
|
+
# @return [Connection] self
|
60
|
+
def delete_configset(name)
|
61
|
+
if configset? name
|
62
|
+
connection.delete("api/cluster/configs/#{name}")
|
63
|
+
end
|
64
|
+
self
|
65
|
+
rescue Faraday::BadRequestError => e
|
66
|
+
msg = e.response[:body]["error"]["msg"]
|
67
|
+
if msg.match?(/not delete ConfigSet/)
|
68
|
+
raise ConfigSetInUseError.new msg
|
69
|
+
else
|
70
|
+
raise e
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
# Pulled from the examples for rubyzip. No idea why it's not just a part
|
75
|
+
# of the normal interface, but I guess I'm not one to judge.
|
76
|
+
class ZipFileGenerator
|
77
|
+
# Initialize with the directory to zip and the location of the output archive.
|
78
|
+
def initialize(input_dir, output_file)
|
79
|
+
@input_dir = input_dir
|
80
|
+
@output_file = output_file
|
81
|
+
end
|
82
|
+
|
83
|
+
# Zip the input directory.
|
84
|
+
def write
|
85
|
+
entries = Dir.entries(@input_dir) - %w[. ..]
|
86
|
+
::Zip::File.open(@output_file, create: true) do |zipfile|
|
87
|
+
write_entries entries, "", zipfile
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
private
|
92
|
+
|
93
|
+
# A helper method to make the recursion work.
|
94
|
+
def write_entries(entries, path, zipfile)
|
95
|
+
entries.each do |e|
|
96
|
+
zipfile_path = (path == "") ? e : File.join(path, e)
|
97
|
+
disk_file_path = File.join(@input_dir, zipfile_path)
|
98
|
+
|
99
|
+
if File.directory? disk_file_path
|
100
|
+
recursively_deflate_directory(disk_file_path, zipfile, zipfile_path)
|
101
|
+
else
|
102
|
+
put_into_archive(disk_file_path, zipfile, zipfile_path)
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def recursively_deflate_directory(disk_file_path, zipfile, zipfile_path)
|
108
|
+
zipfile.mkdir zipfile_path
|
109
|
+
subdir = Dir.entries(disk_file_path) - %w[. ..]
|
110
|
+
write_entries subdir, zipfile_path, zipfile
|
111
|
+
end
|
112
|
+
|
113
|
+
def put_into_archive(disk_file_path, zipfile, zipfile_path)
|
114
|
+
zipfile.add(zipfile_path, disk_file_path)
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
@@ -0,0 +1,158 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "faraday"
|
4
|
+
require "httpx/adapters/faraday"
|
5
|
+
require "logger"
|
6
|
+
|
7
|
+
require_relative "connection/version"
|
8
|
+
require_relative "connection/configset_admin"
|
9
|
+
require_relative "connection/collection_admin"
|
10
|
+
require_relative "connection/alias_admin"
|
11
|
+
require_relative "collection"
|
12
|
+
require_relative "alias"
|
13
|
+
require_relative "configset"
|
14
|
+
require_relative "errors"
|
15
|
+
|
16
|
+
require "forwardable"
|
17
|
+
|
18
|
+
module SolrCloud
|
19
|
+
# The connection object is the basis of all the other stuff. Everything will be created, directly
|
20
|
+
# or indirectly, through the connection.
|
21
|
+
#
|
22
|
+
# For convenience, it forwards #get, #post, #put, and #delete HTTP verbs to the underlying
|
23
|
+
# raw faraday http client.
|
24
|
+
class Connection
|
25
|
+
extend Forwardable
|
26
|
+
|
27
|
+
include ConfigsetAdmin
|
28
|
+
include CollectionAdmin
|
29
|
+
include AliasAdmin
|
30
|
+
|
31
|
+
attr_reader :url, :logger, :raw_connection
|
32
|
+
|
33
|
+
def_delegators :@raw_connection, :get, :post, :delete, :put
|
34
|
+
|
35
|
+
# Create a new connection to talk to solr
|
36
|
+
# @param url [String] URL to the "root" of the solr installation. For a default solr setup, this will
|
37
|
+
# just be the root url (_not_ including the `/solr`)
|
38
|
+
# @param user [String] username for basic auth, if you're using it
|
39
|
+
# @param password [String] password for basic auth, if you're using it
|
40
|
+
# @param logger [#info, :off, nil] An existing logger to pass in. The symbol ":off" means
|
41
|
+
# don't do logging. If left undefined, will create a standard ruby logger to $stdout
|
42
|
+
# @param adapter [Symbol] The underlying http library to use within Faraday
|
43
|
+
def initialize(url:, user: nil, password: nil, logger: nil, adapter: :httpx)
|
44
|
+
@url = url
|
45
|
+
@user = user
|
46
|
+
@password = password
|
47
|
+
@logger = case logger
|
48
|
+
when :off, :none
|
49
|
+
Logger.new(File::NULL, level: Logger::FATAL)
|
50
|
+
when nil
|
51
|
+
Logger.new($stderr, level: Logger::WARN)
|
52
|
+
else
|
53
|
+
logger
|
54
|
+
end
|
55
|
+
@raw_connection = create_raw_connection(url: url, adapter: adapter, user: user, password: password, logger: @logger)
|
56
|
+
bail_if_incompatible!
|
57
|
+
@logger.info("Connected to supported solr at #{url}")
|
58
|
+
end
|
59
|
+
|
60
|
+
# Pass in your own faraday connection
|
61
|
+
# @param faraday_connection [Faraday::Connection] A pre-build faraday connection object
|
62
|
+
def self.new_from_faraday(faraday_connection)
|
63
|
+
c = allocate
|
64
|
+
c.instance_variable_set(:@raw_connection, faraday_connection)
|
65
|
+
c.instance_variable_set(:@url, faraday_connection.build_url.to_s)
|
66
|
+
c
|
67
|
+
end
|
68
|
+
|
69
|
+
# Create a Faraday connection object to base the API client off of
|
70
|
+
# @see #initialize
|
71
|
+
def create_raw_connection(url:, adapter: :httpx, user: nil, password: nil, logger: nil)
|
72
|
+
Faraday.new(request: {params_encoder: Faraday::FlatParamsEncoder}, url: URI(url)) do |faraday|
|
73
|
+
faraday.use Faraday::Response::RaiseError
|
74
|
+
faraday.request :url_encoded
|
75
|
+
if user
|
76
|
+
faraday.request :authorization, :basic, user, password
|
77
|
+
end
|
78
|
+
faraday.request :json
|
79
|
+
faraday.response :json
|
80
|
+
if logger
|
81
|
+
faraday.response :logger, logger
|
82
|
+
end
|
83
|
+
faraday.adapter adapter
|
84
|
+
faraday.headers["Content-Type"] = "application/json"
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
# Allow accessing the raw_connection via "connection". Yes, connection.connection
|
89
|
+
# can be confusing, but it makes the *_admin stuff easier to read.
|
90
|
+
alias_method :connection, :raw_connection
|
91
|
+
|
92
|
+
# Check to see if we can actually talk to the solr in question
|
93
|
+
# raise [UnsupportedSolr] if the solr version isn't at least 8
|
94
|
+
# raise [ConnectionFailed] if we can't connect for some reason
|
95
|
+
def bail_if_incompatible!
|
96
|
+
raise UnsupportedSolr.new("SolrCloud::Connection needs at least solr 8") if major_version < 8
|
97
|
+
raise UnsupportedSolr.new("SolrCloud::Connection only works in solr cloud mode") unless cloud?
|
98
|
+
rescue Faraday::ConnectionFailed
|
99
|
+
raise ConnectionFailed.new("Can't connect to #{url}")
|
100
|
+
end
|
101
|
+
|
102
|
+
# Get basic system info from the server
|
103
|
+
# @raise [Unauthorized] if the server gives a 401
|
104
|
+
# @return [Hash] The response from the info call
|
105
|
+
def system
|
106
|
+
resp = get("/solr/admin/info/system")
|
107
|
+
resp.body
|
108
|
+
rescue Faraday::UnauthorizedError
|
109
|
+
raise Unauthorized.new("Server reports failed authorization")
|
110
|
+
end
|
111
|
+
|
112
|
+
# @return [String] the mode ("solrcloud" or "std") solr is running in
|
113
|
+
def mode
|
114
|
+
system["mode"]
|
115
|
+
end
|
116
|
+
|
117
|
+
# @return [Boolean] whether or not solr is running in cloud mode
|
118
|
+
def cloud?
|
119
|
+
mode == "solrcloud"
|
120
|
+
end
|
121
|
+
|
122
|
+
# @return [String] the major.minor.patch string of the solr version
|
123
|
+
def version_string
|
124
|
+
system["lucene"]["solr-spec-version"]
|
125
|
+
end
|
126
|
+
|
127
|
+
# Helper method to get version parts as ints
|
128
|
+
# @return [Integer] Integerized version of the 0,1,2 portion of the version string
|
129
|
+
def _version_part_int(index)
|
130
|
+
version_string.split(".")[index].to_i
|
131
|
+
end
|
132
|
+
|
133
|
+
# @return [Integer] solr major version
|
134
|
+
def major_version
|
135
|
+
_version_part_int(0)
|
136
|
+
end
|
137
|
+
|
138
|
+
# @return [Integer] solr minor version
|
139
|
+
def minor_version
|
140
|
+
_version_part_int(1)
|
141
|
+
end
|
142
|
+
|
143
|
+
# @return [Integer] solr patch version
|
144
|
+
def patch_version
|
145
|
+
_version_part_int(2)
|
146
|
+
end
|
147
|
+
|
148
|
+
def inspect
|
149
|
+
"<#{self.class} #{@url}>"
|
150
|
+
end
|
151
|
+
|
152
|
+
alias_method :to_s, :inspect
|
153
|
+
|
154
|
+
def pretty_print(q)
|
155
|
+
q.text inspect
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Errors to make it more clear what's going on if things go south
|
4
|
+
module SolrCloud
|
5
|
+
class NoSuchCollectionError < ArgumentError; end
|
6
|
+
|
7
|
+
class NoSuchConfigSetError < ArgumentError; end
|
8
|
+
|
9
|
+
class NoSuchAliasError < ArgumentError; end
|
10
|
+
|
11
|
+
class WontOverwriteError < RuntimeError; end
|
12
|
+
|
13
|
+
class ConfigSetInUseError < RuntimeError; end
|
14
|
+
|
15
|
+
class CollectionAliasedError < RuntimeError; end
|
16
|
+
|
17
|
+
class UnsupportedSolr < RuntimeError; end
|
18
|
+
|
19
|
+
class Unauthorized < ArgumentError; end
|
20
|
+
|
21
|
+
class ConnectionFailed < RuntimeError; end
|
22
|
+
end
|
metadata
ADDED
@@ -0,0 +1,163 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: solr_cloud-connection
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Bill Dueber
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2023-11-30 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: faraday
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 2.7.12
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 2.7.12
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: httpx
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 1.1.5
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: 1.1.5
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rubyzip
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: 2.3.0
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 2.3.0
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: pry
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: dotenv
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: standard
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: simplecov
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - ">="
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ">="
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
111
|
+
description:
|
112
|
+
email:
|
113
|
+
- bill@dueber.com
|
114
|
+
executables: []
|
115
|
+
extensions: []
|
116
|
+
extra_rdoc_files: []
|
117
|
+
files:
|
118
|
+
- ".env.local"
|
119
|
+
- ".rspec"
|
120
|
+
- ".standard.yml"
|
121
|
+
- CHANGELOG.md
|
122
|
+
- Dockerfile
|
123
|
+
- LICENSE.txt
|
124
|
+
- README.md
|
125
|
+
- Rakefile
|
126
|
+
- compose.yml
|
127
|
+
- env.development
|
128
|
+
- lib/solr_cloud/alias.rb
|
129
|
+
- lib/solr_cloud/collection.rb
|
130
|
+
- lib/solr_cloud/configset.rb
|
131
|
+
- lib/solr_cloud/connection.rb
|
132
|
+
- lib/solr_cloud/connection/alias_admin.rb
|
133
|
+
- lib/solr_cloud/connection/collection_admin.rb
|
134
|
+
- lib/solr_cloud/connection/configset_admin.rb
|
135
|
+
- lib/solr_cloud/connection/version.rb
|
136
|
+
- lib/solr_cloud/errors.rb
|
137
|
+
homepage: https://github.com/mlibrary/solr_cloud-connection
|
138
|
+
licenses: []
|
139
|
+
metadata:
|
140
|
+
homepage_uri: https://github.com/mlibrary/solr_cloud-connection
|
141
|
+
source_code_uri: https://github.com/mlibrary/solr_cloud-connection
|
142
|
+
changelog_uri: https://github.com/mlibrary/solr_cloud-connection/CHANGELOG.md
|
143
|
+
post_install_message:
|
144
|
+
rdoc_options: []
|
145
|
+
require_paths:
|
146
|
+
- lib
|
147
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
148
|
+
requirements:
|
149
|
+
- - ">="
|
150
|
+
- !ruby/object:Gem::Version
|
151
|
+
version: 2.6.0
|
152
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
153
|
+
requirements:
|
154
|
+
- - ">="
|
155
|
+
- !ruby/object:Gem::Version
|
156
|
+
version: '0'
|
157
|
+
requirements: []
|
158
|
+
rubygems_version: 3.4.17
|
159
|
+
signing_key:
|
160
|
+
specification_version: 4
|
161
|
+
summary: Do basic administrative operations on a solr cloud instance and collections
|
162
|
+
within
|
163
|
+
test_files: []
|