solr_cloud-connection 0.5.0 → 1.0.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 0d4b1ea9a7eade492c2e88434c38dadc43bcbb5a28084c4be797ce2c77eb70ce
4
- data.tar.gz: 5531b66702ac408e0631239568035f2693cbda02b06e5401b858caa3bd633d9f
3
+ metadata.gz: 19bcfd12d8bc2169aad5f3a01163996e671c0e8b2594ad50197ee7127abf15a9
4
+ data.tar.gz: 9088cfcdf21b11fe5ac92fd35ea2d5ec66ea55471d9a0e3a16edf2304db22bf4
5
5
  SHA512:
6
- metadata.gz: b6fcb28bcf8a42191ce737b4c5ae431d669a66e92b3a2d947e26c7901f3a9b5ec0afd4353d00bf6db4ae397a10c4c8aee629c366fbb6976595170b47b7cf1e2a
7
- data.tar.gz: c8689a6b34f2ba3307ac1bfd19adf2c5fff13ecfd7b6aa2fc934754c9667569b369fddfcb4f796528d3594d5babf3213c603a9871cb92edbbb28a6f78e45e006
6
+ metadata.gz: 61d0a5ce50ec30182bc0e60c915a78b6cc492039291634b53e48efb5fd91124643c97ebafc9ad9043d690bf0f28dbd047c5d1688acfb927c5ca126320d9e00c7
7
+ data.tar.gz: 9771f4d1047ef1210eecd1fc78b376363c7d9d7cd34b4c72023eb9f502aa746c1247844de176d03c9c3db8aec9673fa9d65edd97eb06c7f4d9bb18ec7a70a217
data/CHANGELOG.md CHANGED
@@ -1,5 +1,43 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.0.0] - 2026-07-07
4
+
5
+ ### Major — Solr 8/9/10 Dual-Version Compatibility
6
+
7
+ This release adds support for Solr 9 and 10 alongside the existing Solr 8 support.
8
+ The internal API dispatch now detects the connected Solr major version and routes
9
+ requests to the correct endpoint, since several Solr APIs changed paths or response
10
+ envelopes between Solr 8 and Solr 9/10.
11
+
12
+ - **Feature: Solr 8/9/10 compatibility**
13
+ - Use V1 `CLUSTERSTATUS` action for collection info instead of V2 `api/collections/{name}`
14
+ (the V2 response envelope changed between Solr 8 and Solr 9/10 — the `cluster` wrapper
15
+ key is absent in Solr 10)
16
+ - Use V1 `LISTALIASES` action with `|| {}` guard for nil aliases on Solr 10
17
+ - Use V1 `admin/configs` endpoint for configset upload (path differs between Solr 8 and 9+)
18
+ - Add `solr9_or_later?` predicate to the connection for external version-gating
19
+ - **CI: Matrix testing** — run tests against Solr 8 & Solr 10 in parallel via GitHub Actions
20
+ - **Infrastructure** — Docker Compose healthchecks, env setup for Solr 10 test container, curl-based wait loops
21
+
22
+ ### Chores
23
+ - Replace `wait-for-it` / raw curl loops with Docker Compose healthcheck + `docker compose wait`
24
+ - Add Solr 10 Docker image definition (`spec/data/solr10_docker/`)
25
+ - Update README
26
+ - Version bump to 1.0.0
27
+
28
+ ## [0.6.0] - 2025-05-29
29
+ - Expose `username` and `password` getters on the connection
30
+ - Update README with "Common Usage" section
31
+ - Peg dependency versions in gemspec
32
+ - General gemfile/spec cleanup
33
+
34
+ ## [0.5.0] - 2025-03-24
35
+ - Added some sugar for unifying aliases and collections
36
+ - Update tests
37
+ - Better errors
38
+ - Flesh out README.md to be a useful document
39
+
40
+
3
41
  ## [0.4.0] - 2023-12-07
4
42
  - Fixed rules about what names are legal collections/configsets/aliases
5
43
  - Update version and changelog
data/LICENSE.md ADDED
@@ -0,0 +1,27 @@
1
+ Copyright (c) 2026, The Regents of the University of Michigan.
2
+ All rights reserved.
3
+
4
+ Redistribution and use in source and binary forms, with or without
5
+ modification, are permitted provided that the following conditions are
6
+ met:
7
+
8
+ * Redistributions of source code must retain the above copyright
9
+ notice, this list of conditions and the following disclaimer.
10
+ * Redistributions in binary form must reproduce the above copyright
11
+ notice, this list of conditions and the following disclaimer in the
12
+ documentation and/or other materials provided with the distribution.
13
+ * Neither the name of the The University of Michigan nor the
14
+ names of its contributors may be used to endorse or promote products
15
+ derived from this software without specific prior written permission.
16
+
17
+ THIS SOFTWARE IS PROVIDED BY THE REGENTS OF THE UNIVERSITY OF MICHIGAN AND
18
+ CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT
19
+ NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
20
+ A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OF THE
21
+ UNIVERSITY OF MICHIGAN BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
22
+ SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
23
+ TO,PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
24
+ PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
25
+ LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
26
+ NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
27
+ EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
data/README.md CHANGED
@@ -1,14 +1,36 @@
1
1
  # SolrCloud::Connection
2
2
 
3
- Do basic administrative tasks on a running Solr cloud instance, including:
3
+ ## Common usage
4
4
 
5
- * create (i.e., upload) a configSet when given a `conf` directory
6
- * list, create, and delete configsets, collections, and aliases
7
- * get basic version information for the running solr
8
- * check on the health of individual collections
9
- * treat an alias (mostly) as a collection
10
- * TODO automatically generate methods to talk to defined requestHandlers
11
- * TODO Add something useful for configsets to do
5
+ ```ruby
6
+
7
+ # Connect to the server, upload a config set, make a collection based on it,
8
+ # and make an alias pointing to that collection
9
+
10
+ server = SolrCloud::Connection.new(url: url, user: user, password: pass)
11
+ cfg = server.create_configset(name: "my_cfg", confdir: "/path/to/my/conf")
12
+ cars_v1 = server.create_collection(name: "cars_v1", configset: "my_cfg")
13
+ cars = cars_v1.alias_as("cars")
14
+
15
+ ```
16
+
17
+ ## Basic functionality / Roadmap
18
+
19
+ Do basic administrative tasks on a running Solr cloud instance
20
+
21
+ * [x] create (i.e., upload) a configSet when given a `conf` directory
22
+ * [x] list, create, and delete configsets, collections, and aliases
23
+ * [x] get basic version information for the running solr
24
+ * [x] check on the health of individual collections
25
+ * [x] treat an alias (mostly) as a collection
26
+ * [ ] automatically generate methods to talk to defined requestHandlers and updateHandlers
27
+ * [ ] provide a way to talk to the analyzer for testing of fieldTypes
28
+ * [ ] hook into the schema API
29
+ * [ ] allow it to work with cores, and not just solrcloud collections (which, you know bad naming then)
30
+ * [ ] figure out how to deal with managed resources
31
+ * [ ] get info from updateHandler metrics, esp. pending documents and cumulative errors
32
+ * [ ] hook into performance metrics for easy reporting and checks
33
+ * [ ] support more of the v2 API
12
34
 
13
35
  In almost all cases, you can treat an alias to a collection like the underlying collection.
14
36
 
@@ -43,6 +65,8 @@ user = "solr"
43
65
  password = "SolrRocks"
44
66
  config_directory = "/path/to/myconfig/conf" # Directory 'conf' contains solrconfig.xml
45
67
 
68
+ require "solr_cloud/connection"
69
+
46
70
  server = SolrCloud::Connection.new(url: url, user: user, password: pass)
47
71
  #=> <SolrCloud::Connection http://localhost:9090>
48
72
 
@@ -66,19 +90,25 @@ server.configset_names #=> ["_default"]
66
90
 
67
91
  # Create a new configset by taking a conf directory, zipping it up,
68
92
  # and sending it to solr
69
- cset = server.create_configset(name: "horseless", confdir: config_directory) #=> <SolrCloud::Configset 'horseless' at http://localhost:9090>
93
+ cset = server.create_configset(name: "horseless", confdir: config_directory)
94
+ #=> <SolrCloud::Configset 'horseless' at http://localhost:9090>
70
95
  server.configset_names #=> ["_default", "horseless"]
71
96
 
72
97
  # That's a dumb name for a config set. Delete it and try again.
73
98
  cset.delete! #=> <SolrCloud::Connection http://localhost:9090>
74
- cset = server.create_configset(name: "cars_cfg", confdir: config_directory) #=> <SolrCloud::Configset 'cars_cfg' at http://localhost:9090>
75
- server.configsets #=> [<SolrCloud::Configset '_default' at http://localhost:9090>, <SolrCloud::Configset 'cars_cfg' at http://localhost:9090>]
99
+ cset = server.create_configset(name: "cars_cfg", confdir: config_directory)
100
+ #=> <SolrCloud::Configset 'cars_cfg' at http://localhost:9090>
101
+ server.configsets
102
+ #=> [<SolrCloud::Configset '_default' at http://localhost:9090>,
103
+ # <SolrCloud::Configset 'cars_cfg' at http://localhost:9090>]
76
104
 
77
105
  # Can't be overwritten by accident
78
- server.create_configset(name: "cars_cfg", confdir: config_directory) #=> raised #<SolrCloud::WontOverwriteError: Won't replace configset cars_cfg unless 'force: true' passed >
106
+ server.create_configset(name: "cars_cfg", confdir: config_directory)
107
+ #=> raised #<SolrCloud::WontOverwriteError: Won't replace configset cars_cfg unless 'force: true' passed >
79
108
 
80
109
  # But you can force it
81
- server.create_configset(name: "cars_cfg", confdir: config_directory, force: true) #=> <SolrCloud::Configset 'cars_cfg' at http://localhost:9090>
110
+ server.create_configset(name: "cars_cfg", confdir: config_directory, force: true)
111
+ #=> <SolrCloud::Configset 'cars_cfg' at http://localhost:9090>
82
112
 
83
113
  cfg = server.get_configset("cars_cfg") #=> <SolrCloud::Configset 'cars_cfg' at http://localhost:9090>
84
114
  cfg.in_use? #=> false
@@ -127,7 +157,8 @@ cars_v1.aliased? #=> true
127
157
 
128
158
  cars_v1.has_alias?("cars") #=> true
129
159
  cars_v1.alias_as("autos") #=> <SolrCloud::Alias 'autos' (alias of 'cars_v1')>
130
- cars_v1.aliases #=> [<SolrCloud::Alias 'cars' (alias of 'cars_v1')>, <SolrCloud::Alias 'autos' (alias of 'cars_v1')>]
160
+ cars_v1.aliases
161
+ #=> [<SolrCloud::Alias 'cars' (alias of 'cars_v1')>, <SolrCloud::Alias 'autos' (alias of 'cars_v1')>]
131
162
 
132
163
  cars_v1.get_alias("autos").delete! #=> <SolrCloud::Connection http://localhost:9090>
133
164
  cars_v1.aliases #=> [<SolrCloud::Alias 'cars' (alias of 'cars_v1')>]
@@ -226,5 +257,5 @@ This repository is set up to run tests under docker.
226
257
 
227
258
  ## Contributing
228
259
 
229
- Bugs, functionality suggestions, API suggestions, feature requests, etc. all welcom
260
+ Bugs, functionality suggestions, API suggestions, feature requests, etc. all welcome
230
261
  on GitHub at https://github.com/mlibrary/solr_cloud-connection.
data/Rakefile CHANGED
@@ -10,7 +10,7 @@ require "standard/rake"
10
10
  task default: %i[spec standard docs]
11
11
  task docs: %i[yard]
12
12
 
13
- require 'yard'
13
+ require "yard"
14
14
 
15
15
  YARD::Rake::YardocTask.new do |t|
16
16
  t.files = ["lib/**/*.rb"] # optional
data/compose.yml CHANGED
@@ -11,21 +11,56 @@ services:
11
11
  - .env
12
12
  - env.test
13
13
  command: "tail -f /dev/null"
14
+ depends_on:
15
+ solr:
16
+ condition: service_healthy
17
+ solr10:
18
+ condition: service_healthy
14
19
 
15
20
  solr:
16
21
  build: spec/data/solr_docker/.
17
22
  ports:
18
- - "9090:8983"
23
+ - "6781:8983"
19
24
  environment:
20
25
  - ZK_HOST=zoo:2181
21
26
  depends_on:
22
27
  - zoo
23
28
  command: solr-foreground
29
+ healthcheck:
30
+ test: ["CMD-SHELL", "curl -sf -u solr:SolrRocks http://localhost:8983/solr/admin/info/system || exit 1"]
31
+ interval: 10s
32
+ timeout: 5s
33
+ retries: 12
34
+ start_period: 30s
24
35
 
25
36
  zoo:
26
37
  image: zookeeper
27
38
  ports:
28
- - 9091:2181
39
+ - 6782:2181
40
+ environment:
41
+ ZOO_MY_ID: 1
42
+ ZOO_SERVERS: server.1=0.0.0.0:2888:3888;2181
43
+
44
+ solr10:
45
+ build: spec/data/solr10_docker/.
46
+ ports:
47
+ - "6783:8983"
48
+ environment:
49
+ - ZK_HOST=zoo10:2181
50
+ depends_on:
51
+ - zoo10
52
+ command: solr-foreground
53
+ healthcheck:
54
+ test: ["CMD-SHELL", "curl -sf -u solr:SolrRocks http://localhost:8983/solr/admin/info/system || exit 1"]
55
+ interval: 10s
56
+ timeout: 5s
57
+ retries: 12
58
+ start_period: 30s
59
+
60
+ zoo10:
61
+ image: zookeeper
62
+ ports:
63
+ - 6784:2181
29
64
  environment:
30
65
  ZOO_MY_ID: 1
31
66
  ZOO_SERVERS: server.1=0.0.0.0:2888:3888;2181
data/env.test.solr10 ADDED
@@ -0,0 +1,3 @@
1
+ SOLR_URL="http://solr10:8983"
2
+ SOLR_PASSWORD=SolrRocks
3
+ SOLR_USER=solr
@@ -41,13 +41,13 @@ module SolrCloud
41
41
  # @return [Collection] the now-current collection
42
42
  def switch_collection_to(coll)
43
43
  collect_name = case coll
44
- when String
45
- coll
46
- when Collection
47
- coll.name
48
- else
49
- raise "Alias#switch_collection_to only takes a name(string) or a collection, not '#{coll}'"
50
- end
44
+ when String
45
+ coll
46
+ when Collection
47
+ coll.name
48
+ else
49
+ raise "Alias#switch_collection_to only takes a name(string) or a collection, not '#{coll}'"
50
+ end
51
51
  raise NoSuchCollectionError unless connection.has_collection?(collect_name)
52
52
  connection.create_alias(name: name, collection_name: collect_name, force: true)
53
53
  end
@@ -55,10 +55,10 @@ module SolrCloud
55
55
 
56
56
  def ==(other)
57
57
  case other
58
- when SolrCloud::Collection
59
- self.collection.name == other.collection.name
60
- else
61
- false
58
+ when SolrCloud::Collection
59
+ collection.name == other.collection.name
60
+ else
61
+ false
62
62
  end
63
63
  end
64
64
 
@@ -68,7 +68,7 @@ module SolrCloud
68
68
  def delete!
69
69
  return connection unless exist?
70
70
  raise CollectionAliasedError.new("Cannot delete collection #{name}; it has alias(s) #{alias_names.join(", ")}") if aliased?
71
- connection.get("solr/admin/collections", { action: "DELETE", name: name })
71
+ connection.get("solr/admin/collections", {action: "DELETE", name: name})
72
72
  connection
73
73
  end
74
74
 
@@ -98,8 +98,15 @@ module SolrCloud
98
98
 
99
99
  # Access to the root info from the api. Mostly for internal use, but not labeled as such
100
100
  # 'cause users will almost certainly find a use for it.
101
+ #
102
+ # Uses the V1 +CLUSTERSTATUS+ action rather than the V2 +api/collections/{name}+
103
+ # endpoint because the V2 response envelope changed between Solr 8 and Solr 9/10
104
+ # (the +cluster+ wrapper key is absent in Solr 10). The V1 CLUSTERSTATUS response
105
+ # consistently returns +cluster.collections.{name}+ on all supported versions.
106
+ #
107
+ # @return [Hash] the collection status hash as returned by Solr
101
108
  def info
102
- connection.get("api/collections/#{name}").body["cluster"]["collections"][name]
109
+ connection.get("solr/admin/collections", action: "CLUSTERSTATUS", collection: name).body["cluster"]["collections"][name]
103
110
  end
104
111
 
105
112
  # Reported as healthy?
@@ -181,10 +188,10 @@ module SolrCloud
181
188
 
182
189
  def add(docs)
183
190
  docarray = if docs === Array
184
- docs
185
- else
186
- [docs]
187
- end
191
+ docs
192
+ else
193
+ [docs]
194
+ end
188
195
  post("solr/#{name}/update/") do |r|
189
196
  r.body = docarray
190
197
  end
@@ -200,10 +207,10 @@ module SolrCloud
200
207
  def inspect
201
208
  anames = alias_names
202
209
  astring = if anames.empty?
203
- ""
204
- else
205
- " (aliased by #{anames.map { |x| "'#{x}'" }.join(", ")})"
206
- end
210
+ ""
211
+ else
212
+ " (aliased by #{anames.map { |x| "'#{x}'" }.join(", ")})"
213
+ end
207
214
  "<#{self.class} '#{name}'#{astring}>"
208
215
  end
209
216
 
@@ -7,7 +7,6 @@ module SolrCloud
7
7
  # throw an error if that's an illegal operation (because a collection is
8
8
  # using it)
9
9
  class Configset
10
-
11
10
  # @return [String] the name of this configset
12
11
  attr_reader :name
13
12
 
@@ -5,7 +5,6 @@ module SolrCloud
5
5
  # methods having to do with aliases, to be included by the connection object.
6
6
  # These are split out only to make it easier to deal with them.
7
7
  module AliasAdmin
8
-
9
8
  # A simple data-class to pair an alias with its collection
10
9
  AliasCollectionPair = Struct.new(:alias, :collection)
11
10
 
@@ -24,7 +23,7 @@ module SolrCloud
24
23
  end
25
24
  raise NoSuchCollectionError.new("Can't find collection #{collection_name}") unless has_collection?(collection_name)
26
25
  if has_alias?(name) && !force
27
- raise WontOverwriteError.new("Alias '#{name}' already points to collection '#{self.get_alias(name).collection.name}'; won't overwrite without force: true")
26
+ raise WontOverwriteError.new("Alias '#{name}' already points to collection '#{get_alias(name).collection.name}'; won't overwrite without force: true")
28
27
  end
29
28
  connection.get("solr/admin/collections", action: "CREATEALIAS", name: name, collections: collection_name)
30
29
  get_alias(name)
@@ -75,13 +74,17 @@ module SolrCloud
75
74
  end
76
75
  end
77
76
 
78
- # The "raw" alias map, which just maps alias names to collection names
79
- # @return [Hash<String, String>]
77
+ # The "raw" alias map, which just maps alias names to collection names.
78
+ #
79
+ # The Solr +LISTALIASES+ action omits the +"aliases"+ key entirely (returning +null+
80
+ # or an absent key) when no aliases are defined. This is true on Solr 10 and can
81
+ # occur on earlier versions as well. The +|| {}+ guard ensures callers always receive
82
+ # a Hash rather than +nil+.
83
+ #
84
+ # @return [Hash<String, String>] map of alias name -> collection name, empty if none exist
80
85
  def raw_alias_map
81
- connection.get("solr/admin/collections", action: "LISTALIASES").body["aliases"]
86
+ connection.get("solr/admin/collections", action: "LISTALIASES").body["aliases"] || {}
82
87
  end
83
88
  end
84
89
  end
85
90
  end
86
-
87
-
@@ -20,17 +20,16 @@ module SolrCloud
20
20
  # @raise [WontOverwriteError] if the collection already exists
21
21
  # @return [Collection] the collection created
22
22
  def create_collection(name:, configset:, shards: 1, replication_factor: 1)
23
-
24
23
  unless legal_solr_name?(name)
25
24
  raise IllegalNameError.new("'#{name}' is not a valid solr name. Use only ASCII letters/numbers, dash, and underscore")
26
25
  end
27
26
 
28
27
  configset_name = case configset
29
- when Configset
30
- configset.name
31
- else
32
- configset.to_s
33
- end
28
+ when Configset
29
+ configset.name
30
+ else
31
+ configset.to_s
32
+ end
34
33
  raise WontOverwriteError.new("Collection #{name} already exists") if has_collection?(name)
35
34
  raise NoSuchConfigSetError.new("Configset '#{configset_name}' doesn't exist") unless has_configset?(configset_name)
36
35
 
@@ -4,16 +4,30 @@ require "zip"
4
4
 
5
5
  module SolrCloud
6
6
  class Connection
7
- # methods having to do with configsets, to be included by the connection object.
7
+ # Methods having to do with configsets, to be included by the connection object.
8
8
  # These are split out only to make it easier to deal with them.
9
9
  module ConfigsetAdmin
10
-
11
10
  # Given the path to a solr configuration "conf" directory (i.e., the one with
12
11
  # solrconfig.xml in it), zip it up and send it to solr as a new configset.
12
+ #
13
+ # The upload API endpoint differs across major Solr versions:
14
+ #
15
+ # - *Solr 8*: uses the V2 API (+PUT /api/cluster/configs/<name>+), which was the
16
+ # canonical configset path in Solr 8.
17
+ # - *Solr 9+*: uses the V2 API (+PUT /api/configsets/<name>+).
18
+ # The V2 configset path moved from +/api/cluster/configs/<name>+ (Solr 8) to
19
+ # +/api/configsets/<name>+ (Solr 9+). The new V2 PUT defaults to overwriting an
20
+ # existing configset, which means in-use configsets can be replaced without
21
+ # deleting them first.
22
+ #
23
+ # Dispatches via {Connection#solr9_or_later?}; see {#upload_configset_v9} and
24
+ # {#upload_configset_v8} for the version-specific implementations.
25
+ #
13
26
  # @param name [String] Name to give the new configset
14
27
  # @param confdir [String, Pathname] Path to the solr configuration "conf" directory
15
28
  # @param force [Boolean] Whether or not to overwrite an existing configset if there is one
16
- # @raise [WontOverwriteError] if the configset already exists and "force" is false
29
+ # @raise [IllegalNameError] if +name+ is not a valid Solr identifier
30
+ # @raise [WontOverwriteError] if the configset already exists and +force+ is false
17
31
  # @return [Configset] the configset created
18
32
  def create_configset(name:, confdir:, force: false)
19
33
  config_set_name = name
@@ -24,36 +38,58 @@ module SolrCloud
24
38
  if has_configset?(config_set_name) && !force
25
39
  raise WontOverwriteError.new("Won't replace configset #{config_set_name} unless 'force: true' passed ")
26
40
  end
41
+
27
42
  zfile = "#{Dir.tmpdir}/solr_add_configset_#{name}_#{Time.now.hash}.zip"
28
- z = ZipFileGenerator.new(confdir, zfile)
29
- z.write
30
- @connection.put("api/cluster/configs/#{config_set_name}") do |req|
31
- req.body = File.binread(zfile)
43
+ ZipFileGenerator.new(confdir, zfile).write
44
+
45
+ if solr9_or_later?
46
+ upload_configset_v9(config_set_name, zfile)
47
+ else
48
+ upload_configset_v8(config_set_name, zfile)
32
49
  end
33
- # TODO: Error check in here somewhere
34
- FileUtils.rm(zfile, force: true)
50
+
35
51
  get_configset(name)
52
+ ensure
53
+ FileUtils.rm(zfile, force: true) if zfile
36
54
  end
37
55
 
38
- # Get a list of the already-defined configSets
39
- # @return [Array<Configset>] possibly empty list of configSets
56
+ # Get a list of the already-defined configsets.
57
+ # @return [Array<Configset>] possibly empty list of configsets
40
58
  def configsets
41
59
  configset_names.map { |cs| Configset.new(name: cs, connection: self) }
42
60
  end
43
61
 
62
+ # Get the names of all defined configsets.
63
+ #
64
+ # The API endpoint differs across major Solr versions:
65
+ #
66
+ # - *Solr 8*: uses the V2 API (+GET /api/cluster/configs+).
67
+ # - *Solr 9+*: uses the V1 API (+GET /solr/admin/configs?action=LIST+).
68
+ # The V2 configset path changed between Solr 8 and Solr 9; V1 is used for
69
+ # consistency on Solr 9 and later.
70
+ #
44
71
  # @return [Array<String>] the names of the config sets
45
72
  def configset_names
46
- connection.get("api/cluster/configs").body["configSets"]
73
+ if solr9_or_later?
74
+ connection.get("solr/admin/configs", action: "LIST").body["configSets"]
75
+ else
76
+ connection.get("api/cluster/configs").body["configSets"]
77
+ end
47
78
  end
48
79
 
49
- # Check to see if a configset is defined
50
- # @param name [String] Name of the configSet
80
+ # Check to see if a configset with the given name is defined.
81
+ # @param name [String] Name of the configset
51
82
  # @return [Boolean] Whether a configset with that name exists
52
83
  def has_configset?(name)
53
84
  configset_names.include? name.to_s
54
85
  end
55
86
 
56
- # Get an existing configset
87
+ # Get an existing configset by name.
88
+ #
89
+ # @note Does not verify that the configset actually exists; use {#has_configset?}
90
+ # to check first if needed.
91
+ # @param name [String] Name of the configset
92
+ # @return [Configset]
57
93
  def get_configset(name)
58
94
  Configset.new(name: name, connection: self)
59
95
  end
@@ -64,12 +100,25 @@ module SolrCloud
64
100
  #
65
101
  # In general, prefer using {Configset#delete!} instead of running everything
66
102
  # through the connection object.
67
- # @param [String] name The name of the configuration set
68
- # @raise [InUseError] if the configset can't be deleted because it's in use by a live collection
103
+ #
104
+ # The delete API endpoint differs across major Solr versions:
105
+ #
106
+ # - *Solr 8*: uses the V2 API (+DELETE /api/cluster/configs/<name>+).
107
+ # - *Solr 9+*: uses the V1 API (+GET /solr/admin/configs?action=DELETE+).
108
+ # The V2 configset path changed between Solr 8 and Solr 9; V1 is used for
109
+ # consistency on Solr 9 and later.
110
+ #
111
+ # @param name [String] The name of the configuration set
112
+ # @raise [ConfigSetInUseError] if the configset can't be deleted because it's in use
113
+ # by a live collection
69
114
  # @return [Connection] self
70
115
  def delete_configset(name)
71
- if has_configset? name
72
- connection.delete("api/cluster/configs/#{name}")
116
+ if has_configset?(name)
117
+ if solr9_or_later?
118
+ connection.get("solr/admin/configs", action: "DELETE", name: name)
119
+ else
120
+ connection.delete("api/cluster/configs/#{name}")
121
+ end
73
122
  end
74
123
  self
75
124
  rescue Faraday::BadRequestError => e
@@ -126,6 +175,43 @@ module SolrCloud
126
175
  zipfile.add(zipfile_path, disk_file_path)
127
176
  end
128
177
  end
178
+
179
+ private
180
+
181
+ # Upload a configset zip using the Solr 9+ V2 API.
182
+ #
183
+ # Sends a +PUT+ to +/api/configsets/<name>+ with the zip file as an
184
+ # octet-stream body. The V2 configset upload path moved from
185
+ # +/api/cluster/configs/<name>+ (Solr 8) to +/api/configsets/<name>+ (Solr 9+).
186
+ # The new V2 PUT defaults to overwriting any existing configset at that name,
187
+ # so in-use configsets can be replaced without deleting first.
188
+ #
189
+ # @param name [String] configset name
190
+ # @param zipfile_path [String] filesystem path to the zip archive to upload
191
+ # @return [void]
192
+ def upload_configset_v9(name, zipfile_path)
193
+ connection.put("api/configsets/#{name}") do |req|
194
+ req.headers["Content-Type"] = "application/octet-stream"
195
+ req.body = File.binread(zipfile_path)
196
+ end
197
+ end
198
+
199
+ # Upload a configset zip using the Solr 8 V2 API.
200
+ #
201
+ # Sends a +PUT+ to +/api/cluster/configs/<name>+ with the zip file as an
202
+ # octet-stream body. This was the canonical V2 configset upload path in Solr 8.
203
+ # The path changed to +/api/configsets/<name>+ in Solr 9; use {#upload_configset_v1}
204
+ # for Solr 9 and later.
205
+ #
206
+ # @param name [String] configset name
207
+ # @param zipfile_path [String] filesystem path to the zip archive to upload
208
+ # @return [void]
209
+ def upload_configset_v8(name, zipfile_path)
210
+ connection.put("api/cluster/configs/#{name}") do |req|
211
+ req.headers["Content-Type"] = "application/octet-stream"
212
+ req.body = File.binread(zipfile_path)
213
+ end
214
+ end
129
215
  end
130
216
  end
131
217
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module SolrCloud
4
4
  class Connection
5
- VERSION = "0.5.0"
5
+ VERSION = "1.0.0"
6
6
  end
7
7
  end
@@ -31,12 +31,20 @@ module SolrCloud
31
31
  # @return [String] String representation of the URL to solr
32
32
  attr_reader :url
33
33
 
34
+ # @return [String] solr user
35
+ attr_reader :user
36
+ alias_method :username, :user
37
+
38
+ # @return [String] Solr password
39
+ attr_reader :password
40
+
34
41
  # @return [#info] The logger
35
42
  attr_reader :logger
36
43
 
37
44
  # @return [Faraday::Connection] the underlying Faraday connection
38
45
  attr_reader :connection
39
46
 
47
+
40
48
  # let the underlying connection handle HTTP verbs
41
49
 
42
50
  # @!method get
@@ -72,12 +80,12 @@ module SolrCloud
72
80
  @user = user
73
81
  @password = password
74
82
  @logger = case logger
75
- when :off, :none
76
- Logger.new(File::NULL, level: Logger::FATAL)
77
- when nil
78
- Logger.new($stderr, level: Logger::WARN)
79
- else
80
- logger
83
+ when :off, :none
84
+ Logger.new(File::NULL, level: Logger::FATAL)
85
+ when nil
86
+ Logger.new($stderr, level: Logger::WARN)
87
+ else
88
+ logger
81
89
  end
82
90
  @connection = create_raw_connection(url: url, adapter: adapter, user: user, password: password, logger: @logger)
83
91
  bail_if_incompatible!
@@ -112,10 +120,6 @@ module SolrCloud
112
120
  end
113
121
  end
114
122
 
115
- # Allow accessing the raw_connection via "connection". Yes, connection.connection
116
- # can be confusing, but it makes the *_admin stuff easier to read.
117
- alias_method :connection, :connection
118
-
119
123
  # Check to see if we can actually talk to the solr in question
120
124
  # raise [UnsupportedSolr] if the solr version isn't at least 8
121
125
  # raise [ConnectionFailed] if we can't connect for some reason
@@ -172,6 +176,17 @@ module SolrCloud
172
176
  _version_part_int(2)
173
177
  end
174
178
 
179
+ # Returns true if the connected Solr instance is version 9 or later (9, 10, 11, ...).
180
+ #
181
+ # Several Solr APIs changed paths between Solr 8 and Solr 9. This predicate is used
182
+ # internally to dispatch to the correct API endpoint when behavior differs across major
183
+ # versions. External callers may also use it to gate version-specific logic.
184
+ #
185
+ # @return [Boolean] true if the server reports major version >= 9
186
+ def solr9_or_later?
187
+ major_version >= 9
188
+ end
189
+
175
190
  # Check to see if the given string follows solr's rules for thing
176
191
  # Solr only allows ASCII letters and numbers, underscore, and dash,
177
192
  # and it can't start with an underscore.
@@ -192,6 +207,3 @@ module SolrCloud
192
207
  end
193
208
  end
194
209
  end
195
-
196
-
197
-
data/readme.rb CHANGED
@@ -1,4 +1,5 @@
1
1
  # frozen_string_literal: true
2
+
2
3
  require "pathname"
3
4
  $LOAD_PATH.unshift Pathname.new(__dir__) + "lib"
4
5
  require_relative "lib/solr_cloud/connection"
@@ -7,13 +8,11 @@ url = "http://localhost:9090"
7
8
  user = "solr"
8
9
  pass = "SolrRocks"
9
10
 
10
-
11
11
  server = SolrCloud::Connection.new(url: url, user: user, password: pass) #=>
12
12
 
13
-
14
- server.aliases.each {|a| a.delete!}
15
- server.collections.each {|c| c.delete!}
16
- server.configsets.reject {|c| c.name == "_default"}.each {|c| c.delete!}
13
+ server.aliases.each { |a| a.delete! }
14
+ server.collections.each { |c| c.delete! }
15
+ server.configsets.reject { |c| c.name == "_default" }.each { |c| c.delete! }
17
16
 
18
17
  # or bring your own Faraday object
19
18
  # server2 = SolrCloud::Connection.new_with_faraday(faraday_connection)
@@ -83,8 +82,6 @@ begin
83
82
  rescue
84
83
  end
85
84
 
86
-
87
-
88
85
  ##### Aliases
89
86
 
90
87
  # We'll want to alias it so we can just use 'cars'
@@ -125,9 +122,6 @@ cars.switch_collection_to("cars_v1")
125
122
  server.collection_names
126
123
 
127
124
  # They even == to each other
128
- cars
129
- cars == cars_v1
130
- cars == cars_v2
131
125
 
132
126
  # But sometimes you want to differentiate them from each other
133
127
  server.only_collection_names
@@ -0,0 +1,290 @@
1
+ # Solr 8 / 9 / 10 Compatibility Update Plan
2
+
3
+ ## Overview
4
+
5
+ Make `solr_cloud-connection` work correctly with Solr 8, 9, and 10 simultaneously. The approach:
6
+ branch on `major_version` where APIs differ, and use V1 (legacy) APIs where they work uniformly
7
+ across versions.
8
+
9
+ Version detection is already in place in `connection.rb` via `major_version`, `minor_version`,
10
+ and `patch_version` methods derived from `GET /solr/admin/info/system`.
11
+
12
+ Source examined: `main` branch and `fix/solr-10-api-compatibility` branch.
13
+ API docs source: Apache Solr Reference Guide 10.0 (live, fetched May 2026).
14
+
15
+ ---
16
+
17
+ ## Root Cause
18
+
19
+ The main branch uses Solr 8-era V2 API paths for configset management
20
+ (`api/cluster/configs/*`). These paths changed in Solr 9/10 to `api/configsets/*`.
21
+ Additionally, the V1 `LISTALIASES` action returns `null` (not `{}`) in Solr 10 when
22
+ no aliases exist, causing a `NoMethodError` on `nil.keys`.
23
+
24
+ The `fix/solr-10-api-compatibility` branch already addresses this by switching configset
25
+ operations wholesale to V1 APIs and adding a `|| {}` nil guard, but it does so without
26
+ version branching. This plan implements proper version-based dispatch.
27
+
28
+ ---
29
+
30
+ ## API Compatibility Matrix
31
+
32
+ | Method / File | Solr 8 (main branch) | Solr 9/10 compat? | Notes |
33
+ |---|---|---|---|
34
+ | `configset_admin#create_configset` | `PUT api/cluster/configs/{name}` (V2) | NO | V2 path changed |
35
+ | `configset_admin#configset_names` | `GET api/cluster/configs` (V2) | NO | V2 path changed |
36
+ | `configset_admin#delete_configset` | `DELETE api/cluster/configs/{name}` (V2) | NO | V2 path changed |
37
+ | `alias_admin#raw_alias_map` | `GET solr/admin/collections?action=LISTALIASES` | PARTIAL | returns nil when empty |
38
+ | `alias_admin#create_alias` | V1 CREATEALIAS | YES | V1 works in all versions |
39
+ | `alias#delete!` | V1 DELETEALIAS | YES | V1 works in all versions |
40
+ | `collection_admin#only_collections` | `GET api/collections` (V2) | YES | same response format |
41
+ | `collection_admin#create_collection` | V1 CREATE | YES | V1 works in all versions |
42
+ | `collection#delete!` | V1 DELETE | YES | V1 works in all versions |
43
+ | `collection#info` | `GET api/collections/{name}` (V2) | UNKNOWN | needs testing; see below |
44
+
45
+ ---
46
+
47
+ ## Changes Required
48
+
49
+ ### 1. `connection.rb` -- add version-helper predicate
50
+
51
+ Add a convenience predicate to avoid repeating `major_version >= 9` comparisons inline:
52
+
53
+ ```ruby
54
+ # Returns true for Solr 9 and later (including 10, 11, ...)
55
+ def solr9_or_later?
56
+ major_version >= 9
57
+ end
58
+ ```
59
+
60
+ Place it alongside the existing `major_version` / `minor_version` / `patch_version` methods.
61
+ No changes to `bail_if_incompatible!`.
62
+
63
+ ---
64
+
65
+ ### 2. `connection/configset_admin.rb` -- version-branch all three methods
66
+
67
+ The V2 configset paths changed between Solr 8 and Solr 9:
68
+
69
+ | Operation | Solr 8 V2 path | Solr 9/10 V2 path | V1 path (all versions) |
70
+ |---|---|---|---|
71
+ | Upload | `PUT api/cluster/configs/{name}` | `PUT api/configsets/{name}` | `POST solr/admin/configs?action=UPLOAD&name={name}` |
72
+ | List | `GET api/cluster/configs` -> `["configSets"]` | `GET api/configsets` -> `["configSets"]` | `GET solr/admin/configs?action=LIST` -> `["configSets"]` |
73
+ | Delete | `DELETE api/cluster/configs/{name}` | `DELETE api/configsets/{name}` | `GET solr/admin/configs?action=DELETE&name={name}` |
74
+
75
+ Strategy: use V1 for Solr 9+, keep existing V2 for Solr 8. V1 works in Solr 8 too, so
76
+ an alternative is to use V1 for all versions and avoid branching entirely -- but the task
77
+ requires explicit version dispatch.
78
+
79
+ #### `create_configset`
80
+
81
+ ```ruby
82
+ def create_configset(name:, confdir:, force: false)
83
+ raise WontOverwriteError.new("Configset '#{name}' already exists") if has_configset?(name) && !force
84
+ zipfile = zip_configset_dir(confdir)
85
+ if connection.solr9_or_later?
86
+ connection.post("solr/admin/configs?action=UPLOAD&name=#{name}") do |req|
87
+ req.headers["Content-Type"] = "application/octet-stream"
88
+ req.body = File.binread(zipfile)
89
+ end
90
+ else
91
+ connection.put("api/cluster/configs/#{name}") do |req|
92
+ req.headers["Content-Type"] = "application/octet-stream"
93
+ req.body = File.binread(zipfile)
94
+ end
95
+ end
96
+ get_configset(name)
97
+ ensure
98
+ FileUtils.rm_f(zipfile) if zipfile
99
+ end
100
+ ```
101
+
102
+ Note: the `fix` branch uses `connection.post("solr/admin/configs", action: "UPLOAD",
103
+ name: name)` but that places action/name as query params on a POST. The Solr V1 API
104
+ docs specify these as query string parameters for the UPLOAD action, so either form works.
105
+ Verify with integration tests.
106
+
107
+ #### `configset_names`
108
+
109
+ ```ruby
110
+ def configset_names
111
+ if connection.solr9_or_later?
112
+ connection.get("solr/admin/configs", action: "LIST").body["configSets"]
113
+ else
114
+ connection.get("api/cluster/configs").body["configSets"]
115
+ end
116
+ end
117
+ ```
118
+
119
+ #### `delete_configset`
120
+
121
+ ```ruby
122
+ def delete_configset(name:)
123
+ return unless has_configset?(name)
124
+ if connection.solr9_or_later?
125
+ connection.get("solr/admin/configs", action: "DELETE", name: name)
126
+ else
127
+ connection.delete("api/cluster/configs/#{name}")
128
+ end
129
+ end
130
+ ```
131
+
132
+ ---
133
+
134
+ ### 3. `connection/alias_admin.rb` -- nil guard (bug fix, not version-specific)
135
+
136
+ `raw_alias_map` crashes with `NoMethodError` when no aliases exist because the Solr
137
+ `LISTALIASES` response omits the `aliases` key entirely (returns `null` or simply absent).
138
+ This is confirmed on Solr 10 and the fix is straightforward:
139
+
140
+ ```ruby
141
+ def raw_alias_map
142
+ connection.get("solr/admin/collections", action: "LISTALIASES").body["aliases"] || {}
143
+ end
144
+ ```
145
+
146
+ This fix is already present in `fix/solr-10-api-compatibility`. It is not version-specific --
147
+ apply it unconditionally, as it is defensive coding.
148
+
149
+ ---
150
+
151
+ ### 4. `lib/solr_cloud/collection.rb` -- verify `info` endpoint (needs testing)
152
+
153
+ `Collection#info` calls:
154
+
155
+ ```ruby
156
+ connection.get("api/collections/#{name}").body["cluster"]["collections"][name]
157
+ ```
158
+
159
+ The response path `body["cluster"]["collections"][name]` matches the structure returned by
160
+ the V1 `CLUSTERSTATUS` API and the V2 `GET /api/cluster` endpoint. Whether
161
+ `GET /api/collections/{name}` returns this same nested structure in Solr 9/10 is not
162
+ confirmed from docs alone.
163
+
164
+ **Action required:**
165
+ - Run integration tests against Solr 9 and Solr 10 containers with the existing code
166
+ - If `api/collections/{name}` returns a different response structure in Solr 9/10,
167
+ add version branching:
168
+
169
+ ```ruby
170
+ def info
171
+ if connection.solr9_or_later?
172
+ # Fallback to V1 CLUSTERSTATUS filtered to this collection
173
+ connection.get("solr/admin/collections",
174
+ action: "CLUSTERSTATUS",
175
+ collection: name
176
+ ).body["cluster"]["collections"][name]
177
+ else
178
+ connection.get("api/collections/#{name}").body["cluster"]["collections"][name]
179
+ end
180
+ end
181
+ ```
182
+
183
+ V1 `CLUSTERSTATUS` with `collection` param is confirmed to work in both Solr 8 and 10
184
+ and returns the exact same `cluster.collections.{name}` structure.
185
+
186
+ ---
187
+
188
+ ### 5. No changes needed
189
+
190
+ The following use V1 APIs or V2 APIs that are stable across Solr 8, 9, and 10:
191
+
192
+ - `alias_admin#create_alias` -- V1 `CREATEALIAS`
193
+ - `alias#delete!` -- V1 `DELETEALIAS`
194
+ - `collection_admin#create_collection` -- V1 `CREATE`
195
+ - `collection_admin#only_collections` -- `GET api/collections` response format confirmed
196
+ unchanged in Solr 10 (`{"collections": [...]}`)
197
+ - `collection#delete!` -- V1 `DELETE`
198
+ - `connection#system` / `version_string` / `major_version` etc. -- `/solr/admin/info/system`
199
+ unchanged
200
+
201
+ ---
202
+
203
+ ## Implementation Pattern
204
+
205
+ Use private dispatch methods to keep the public interface clean:
206
+
207
+ ```ruby
208
+ # In ConfigsetAdmin module:
209
+
210
+ def create_configset(name:, confdir:, force: false)
211
+ raise WontOverwriteError.new(...) if has_configset?(name) && !force
212
+ zipfile = zip_configset_dir(confdir)
213
+ solr9_or_later? ? upload_configset_v1(name, zipfile) : upload_configset_v8(name, zipfile)
214
+ get_configset(name)
215
+ ensure
216
+ FileUtils.rm_f(zipfile) if zipfile
217
+ end
218
+
219
+ private
220
+
221
+ def upload_configset_v1(name, zipfile)
222
+ connection.post("solr/admin/configs?action=UPLOAD&name=#{name}") do |req|
223
+ req.headers["Content-Type"] = "application/octet-stream"
224
+ req.body = File.binread(zipfile)
225
+ end
226
+ end
227
+
228
+ def upload_configset_v8(name, zipfile)
229
+ connection.put("api/cluster/configs/#{name}") do |req|
230
+ req.headers["Content-Type"] = "application/octet-stream"
231
+ req.body = File.binread(zipfile)
232
+ end
233
+ end
234
+ ```
235
+
236
+ Where the dispatch is simple (one line each branch), inline `if/else` is acceptable.
237
+ Prefer private methods when the per-version logic is more than 2 lines.
238
+
239
+ ---
240
+
241
+ ## Test Infrastructure
242
+
243
+ The existing spec infrastructure uses Docker Compose in `spec/data/solr_docker/`.
244
+
245
+ Add a Solr 10 service (and optionally Solr 9) alongside the existing Solr 8 service:
246
+
247
+ ```yaml
248
+ # In spec/data/solr_docker/docker-compose.yml (new service):
249
+ solr10:
250
+ image: solr:10-slim
251
+ ports:
252
+ - "18984:8983"
253
+ command: solr-demo
254
+ # or whatever start command the existing solr8 service uses
255
+ ```
256
+
257
+ Update the integration test runner (likely a Rake task or script) to run the full spec
258
+ suite twice: once against Solr 8 and once against Solr 10 (passing the base URL as an
259
+ environment variable).
260
+
261
+ If the test suite already accepts a `SOLR_URL` environment variable, this is straightforward.
262
+ If it hardcodes a port, parameterize it.
263
+
264
+ ---
265
+
266
+ ## Implementation Order
267
+
268
+ 1. Add `solr9_or_later?` to `connection.rb`
269
+ 2. Apply `|| {}` nil guard to `alias_admin.rb#raw_alias_map` (already in fix branch --
270
+ cherry-pick or re-apply)
271
+ 3. Version-branch `configset_admin.rb`: `configset_names`, `delete_configset`,
272
+ `create_configset` (in that order -- names and delete first so tests can verify the
273
+ cycle without broken upload)
274
+ 4. Add Solr 10 container to Docker Compose test setup
275
+ 5. Run integration tests against both Solr 8 and Solr 10
276
+ 6. If `Collection#info` fails against Solr 10, add version-branch (step 4 above)
277
+ 7. Update gem version (patch or minor, depending on whether the nil-guard fix is considered
278
+ a breaking change)
279
+
280
+ ---
281
+
282
+ ## Notes on the fix/solr-10-api-compatibility Branch
283
+
284
+ That branch's approach (switch all configset ops to V1 unconditionally) is functionally
285
+ correct and simpler. If backward compatibility with the V2 Solr 8 behavior is not a concern,
286
+ that approach is acceptable and lower-risk than version branching. The user has explicitly
287
+ requested version-based dispatch, which this plan implements.
288
+
289
+ If Solr 8 V2 API support is dropped in a future major version of the gem, the branches
290
+ can be collapsed to use V1 everywhere.
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: solr_cloud-connection
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.0
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Bill Dueber
8
- autorequire:
9
8
  bindir: bin
10
9
  cert_chain: []
11
- date: 2024-03-22 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: faraday
@@ -53,76 +52,107 @@ dependencies:
53
52
  - !ruby/object:Gem::Version
54
53
  version: '2.0'
55
54
  - !ruby/object:Gem::Dependency
56
- name: pry
55
+ name: rake
57
56
  requirement: !ruby/object:Gem::Requirement
58
57
  requirements:
59
- - - ">="
58
+ - - "~>"
60
59
  - !ruby/object:Gem::Version
61
- version: '0'
60
+ version: '13.0'
62
61
  type: :development
63
62
  prerelease: false
64
63
  version_requirements: !ruby/object:Gem::Requirement
65
64
  requirements:
66
- - - ">="
65
+ - - "~>"
67
66
  - !ruby/object:Gem::Version
68
- version: '0'
67
+ version: '13.0'
69
68
  - !ruby/object:Gem::Dependency
70
- name: dotenv
69
+ name: rspec
71
70
  requirement: !ruby/object:Gem::Requirement
72
71
  requirements:
73
- - - ">="
72
+ - - "~>"
74
73
  - !ruby/object:Gem::Version
75
- version: '0'
74
+ version: '3.0'
76
75
  type: :development
77
76
  prerelease: false
78
77
  version_requirements: !ruby/object:Gem::Requirement
79
78
  requirements:
80
- - - ">="
79
+ - - "~>"
81
80
  - !ruby/object:Gem::Version
82
- version: '0'
81
+ version: '3.0'
83
82
  - !ruby/object:Gem::Dependency
84
83
  name: standard
85
84
  requirement: !ruby/object:Gem::Requirement
86
85
  requirements:
87
86
  - - ">="
88
87
  - !ruby/object:Gem::Version
89
- version: '0'
88
+ version: 1.35.0
89
+ - - "~>"
90
+ - !ruby/object:Gem::Version
91
+ version: '1.0'
90
92
  type: :development
91
93
  prerelease: false
92
94
  version_requirements: !ruby/object:Gem::Requirement
93
95
  requirements:
94
96
  - - ">="
95
97
  - !ruby/object:Gem::Version
96
- version: '0'
98
+ version: 1.35.0
99
+ - - "~>"
100
+ - !ruby/object:Gem::Version
101
+ version: '1.0'
97
102
  - !ruby/object:Gem::Dependency
98
103
  name: simplecov
99
104
  requirement: !ruby/object:Gem::Requirement
100
105
  requirements:
101
106
  - - ">="
102
107
  - !ruby/object:Gem::Version
103
- version: '0'
108
+ version: 0.22.0
109
+ - - "~>"
110
+ - !ruby/object:Gem::Version
111
+ version: '0.0'
104
112
  type: :development
105
113
  prerelease: false
106
114
  version_requirements: !ruby/object:Gem::Requirement
107
115
  requirements:
108
116
  - - ">="
109
117
  - !ruby/object:Gem::Version
110
- version: '0'
118
+ version: 0.22.0
119
+ - - "~>"
120
+ - !ruby/object:Gem::Version
121
+ version: '0.0'
111
122
  - !ruby/object:Gem::Dependency
112
123
  name: yard
113
124
  requirement: !ruby/object:Gem::Requirement
114
125
  requirements:
115
126
  - - ">="
116
127
  - !ruby/object:Gem::Version
117
- version: '0'
128
+ version: 0.9.0
129
+ - - "~>"
130
+ - !ruby/object:Gem::Version
131
+ version: 0.9.0
118
132
  type: :development
119
133
  prerelease: false
120
134
  version_requirements: !ruby/object:Gem::Requirement
121
135
  requirements:
122
136
  - - ">="
123
137
  - !ruby/object:Gem::Version
124
- version: '0'
125
- description:
138
+ version: 0.9.0
139
+ - - "~>"
140
+ - !ruby/object:Gem::Version
141
+ version: 0.9.0
142
+ - !ruby/object:Gem::Dependency
143
+ name: dotenv
144
+ requirement: !ruby/object:Gem::Requirement
145
+ requirements:
146
+ - - "~>"
147
+ - !ruby/object:Gem::Version
148
+ version: '3.0'
149
+ type: :development
150
+ prerelease: false
151
+ version_requirements: !ruby/object:Gem::Requirement
152
+ requirements:
153
+ - - "~>"
154
+ - !ruby/object:Gem::Version
155
+ version: '3.0'
126
156
  email:
127
157
  - bill@dueber.com
128
158
  executables: []
@@ -134,11 +164,13 @@ files:
134
164
  - ".standard.yml"
135
165
  - CHANGELOG.md
136
166
  - Dockerfile
167
+ - LICENSE.md
137
168
  - LICENSE.txt
138
169
  - README.md
139
170
  - Rakefile
140
171
  - compose.yml
141
172
  - env.test
173
+ - env.test.solr10
142
174
  - lib/solr_cloud/alias.rb
143
175
  - lib/solr_cloud/collection.rb
144
176
  - lib/solr_cloud/configset.rb
@@ -149,13 +181,13 @@ files:
149
181
  - lib/solr_cloud/connection/version.rb
150
182
  - lib/solr_cloud/errors.rb
151
183
  - readme.rb
184
+ - solr10_compat_update_plan.md
152
185
  homepage: https://github.com/mlibrary/solr_cloud-connection
153
186
  licenses: []
154
187
  metadata:
155
188
  homepage_uri: https://github.com/mlibrary/solr_cloud-connection
156
189
  source_code_uri: https://github.com/mlibrary/solr_cloud-connection
157
190
  changelog_uri: https://github.com/mlibrary/solr_cloud-connection/CHANGELOG.md
158
- post_install_message:
159
191
  rdoc_options: []
160
192
  require_paths:
161
193
  - lib
@@ -170,8 +202,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
170
202
  - !ruby/object:Gem::Version
171
203
  version: '0'
172
204
  requirements: []
173
- rubygems_version: 3.4.17
174
- signing_key:
205
+ rubygems_version: 3.6.7
175
206
  specification_version: 4
176
207
  summary: Do basic administrative operations on a solr cloud instance and collections
177
208
  within