elasticgraph-local 0.17.1.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,538 @@
1
+ # Copyright 2024 Block, Inc.
2
+ #
3
+ # Use of this source code is governed by an MIT-style
4
+ # license that can be found in the LICENSE file or at
5
+ # https://opensource.org/licenses/MIT.
6
+ #
7
+ # frozen_string_literal: true
8
+
9
+ require "elastic_graph/admin/rake_tasks"
10
+ require "elastic_graph/local/docker_runner"
11
+ require "elastic_graph/schema_definition/rake_tasks"
12
+ require "rake/tasklib"
13
+ require "shellwords"
14
+
15
+ module ElasticGraph
16
+ # Provides support for developing and running ElasticGraph applications locally.
17
+ module Local
18
+ # Defines tasks for local development. These tasks include:
19
+ #
20
+ # - Running OpenSearch and/or Elasticsearch locally (`(elasticsearch|opensearch):[env]:(boot|daemon|halt)`)
21
+ # - Managing schema artifacts (`schema_artifacts:(check|dump)`)
22
+ # - Configuring OpenSearch/Elasticsearch locally (`clusters:configure:(dry_run|perform)`)
23
+ # - Indexing fake data (`index_fake_data:[type]`)
24
+ # - Booting an ElasticGraph application locally (`boot_locally`)
25
+ #
26
+ # @note All tasks (besides the `schema_artifacts` tasks) require `docker` and `docker-compose` to be available on your machine.
27
+ class RakeTasks < ::Rake::TaskLib
28
+ # When enabled, ElasticGraph will configure the index mappings so that the datastore indexes a `_size` field in each index document.
29
+ # ElasticGraph itself does not do anything with this field, but it will be available for your use in any direct queries (e.g. via
30
+ # Kibana).
31
+ #
32
+ # Defaults to `false` since it requires a plugin.
33
+ #
34
+ # @note Enabling this requires the [mapper-size plugin](https://www.elastic.co/guide/en/elasticsearch/plugins/8.15/mapper-size.html)
35
+ # to be installed on your datastore cluster. You are responsible for ensuring that is installed if you enable this feature. If you
36
+ # enable this and the plugin is not installed, you will get errors!
37
+ #
38
+ # @return [Boolean] whether or not the `_size` field should be indexed on each indexed type
39
+ #
40
+ # @example Enable indexing document sizes
41
+ # ElasticGraph::Local::RakeTasks.new(
42
+ # local_config_yaml: "config/settings/local.yaml",
43
+ # path_to_schema: "config/schema.rb"
44
+ # ) do |tasks|
45
+ # tasks.index_document_sizes = true
46
+ # end
47
+ #
48
+ # @dynamic index_document_sizes, index_document_sizes=
49
+ attr_accessor :index_document_sizes
50
+
51
+ # The form of names for schema elements (fields, arguments, directives) generated by ElasticGraph, either `:snake_case` or
52
+ # `:camelCase`. For example, if set to `:camelCase`, ElasticGraph will generate a `groupedBy` field, but if set to `:snake_case`,
53
+ # ElasticGraph will generate a `grouped_by` field.
54
+ #
55
+ # Defaults to `:camelCase` since most GraphQL schemas use that casing.
56
+ #
57
+ # @return [:camelCase, :snake_case] which form to use
58
+ #
59
+ # @example Use `snake_case` names instead of `camelCase`
60
+ # ElasticGraph::Local::RakeTasks.new(
61
+ # local_config_yaml: "config/settings/local.yaml",
62
+ # path_to_schema: "config/schema.rb"
63
+ # ) do |tasks|
64
+ # tasks.schema_element_name_form = :snake_case
65
+ # end
66
+ #
67
+ # @dynamic schema_element_name_form, schema_element_name_form=
68
+ attr_accessor :schema_element_name_form
69
+
70
+ # Overrides for specific names of schema elements (fields, arguments, directives) generated by ElasticGraph. For example, to rename
71
+ # the `gt` filter field to `greaterThan`, set to `{gt: "greaterThan"}`.
72
+ #
73
+ # Defaults to an empty hash.
74
+ #
75
+ # @return [Hash<Symbol, String>] overrides for specific field, argument, or directive names
76
+ #
77
+ # @example Spell out comparison operators instead of using shortened forms
78
+ # ElasticGraph::Local::RakeTasks.new(
79
+ # local_config_yaml: "config/settings/local.yaml",
80
+ # path_to_schema: "config/schema.rb"
81
+ # ) do |tasks|
82
+ # tasks.schema_element_name_overrides = {
83
+ # gt: "greaterThan",
84
+ # gte: "greaterThanOrEqualTo",
85
+ # lt: "lessThan",
86
+ # lte: "lessThanOrEqualTo"
87
+ # }
88
+ # end
89
+ #
90
+ # @dynamic schema_element_name_overrides, schema_element_name_overrides=
91
+ attr_accessor :schema_element_name_overrides
92
+
93
+ # Overrides for the naming formats used by ElasticGraph for derived GraphQL type names. For example, to use `Metrics` instead of
94
+ # `AggregatedValues` as the suffix for the generated types supporting getting aggregated metrid values, set to
95
+ # `{AggregatedValues: "%{base}Metrics"}`. See {SchemaDefinition::SchemaElements::TypeNamer::DEFAULT_FORMATS} for the available
96
+ # formats.
97
+ #
98
+ # Defaults to an empty hash.
99
+ #
100
+ # @example Change the `AggregatedValues` type suffix to `Metrics`
101
+ # ElasticGraph::Local::RakeTasks.new(
102
+ # local_config_yaml: "config/settings/local.yaml",
103
+ # path_to_schema: "config/schema.rb"
104
+ # ) do |tasks|
105
+ # tasks.derived_type_name_formats = {AggregatedValues: "Metrics"}
106
+ # end
107
+ #
108
+ # @dynamic derived_type_name_formats, derived_type_name_formats=
109
+ attr_accessor :derived_type_name_formats
110
+
111
+ # Overrides for the names of specific GraphQL types. For example, to rename the `JsonSafeLong` scalar to `BigInt`, set to
112
+ # `{JsonSafeLong: "BigInt}`.
113
+ #
114
+ # Defaults to an empty hash.
115
+ #
116
+ # @return [Hash<Symbol, String>] overrides for specific type names
117
+ #
118
+ # @example Rename `JsonSafeLong` to `BigInt`
119
+ # ElasticGraph::Local::RakeTasks.new(
120
+ # local_config_yaml: "config/settings/local.yaml",
121
+ # path_to_schema: "config/schema.rb"
122
+ # ) do |tasks|
123
+ # tasks.type_name_overrides = {JsonSafeLong: "BigInt"}
124
+ # end
125
+ #
126
+ # @dynamic type_name_overrides, type_name_overrides=
127
+ attr_accessor :type_name_overrides
128
+
129
+ # Overrides for the names of specific GraphQL enum values for specific enum types. For example, to rename the `DayOfWeek.MONDAY`
130
+ # enum to `DayOfWeek.MON`, set to `{DayOfWeek: {MONDAY: "MON"}}`.
131
+ #
132
+ # Defaults to an empty hash.
133
+ #
134
+ # @return [Hash<Symbol, Hash<Symbol, String>>] overrides for the names of specific enum values for specific enum types
135
+ #
136
+ # @example Shorten the names of the `DayOfWeek` enum values
137
+ # ElasticGraph::Local::RakeTasks.new(
138
+ # local_config_yaml: "config/settings/local.yaml",
139
+ # path_to_schema: "config/schema.rb"
140
+ # ) do |tasks|
141
+ # tasks.enum_value_overrides_by_type = {
142
+ # DayOfWeek: {
143
+ # MONDAY: "MON",
144
+ # TUESDAY: "TUE",
145
+ # WEDNESDAY: "WED",
146
+ # THURSDAY: "THU",
147
+ # FRIDAY: "FRI",
148
+ # SATURDAY: "SAT",
149
+ # SUNDAY: "SUN"
150
+ # }
151
+ # }
152
+ # end
153
+ #
154
+ # @dynamic enum_value_overrides_by_type, enum_value_overrides_by_type=
155
+ attr_accessor :enum_value_overrides_by_type
156
+
157
+ # List of Ruby modules to extend onto the {SchemaDefinition::API} instance. Designed to support ElasticGraph extensions (such as
158
+ # `elasticgraph-apollo`). Defaults to an empty list.
159
+ #
160
+ # @return [Array<Module>] list of extension modules
161
+ #
162
+ # @example Extension that defines a `@since` directive and offers a `since` API on fields
163
+ # module SinceExtension
164
+ # # `self.extended` is a standard Ruby hook that gets called when a module is extended onto an object.
165
+ # # The argument is the object the module was extended onto (a `SchemaDefinition::API` instance in this case).
166
+ # def self.extended(api)
167
+ # # Define our `@since` directive
168
+ # api.raw_sdl "directive @since(date: Date!) on FIELD_DEFINITION"
169
+ #
170
+ # # In order to hook into fields, extend the `SchemaDefinition::Factory` with a module. The factory is used
171
+ # # for creation of all schema definition objects.
172
+ # api.factory.extend FactoryExtension
173
+ # end
174
+ #
175
+ # module FactoryExtension
176
+ # # Hook into the creation of all `SchemaDefinition::Field` objects so that we can extend each field
177
+ # # instance with our `FieldExtension` module.
178
+ # def new_field(*args, **options)
179
+ # super(*args, **options) do |field|
180
+ # field.extend FieldExtension
181
+ # yield field if block_given?
182
+ # end
183
+ # end
184
+ # end
185
+ #
186
+ # # Offer a `f.since date` API on fields.
187
+ # module FieldExtension
188
+ # def since(date)
189
+ # directive "since", date: date
190
+ # end
191
+ # end
192
+ # end
193
+ #
194
+ # ElasticGraph::Local::RakeTasks.new(
195
+ # local_config_yaml: "config/settings/local.yaml",
196
+ # path_to_schema: "config/schema.rb"
197
+ # ) do |tasks|
198
+ # tasks.schema_definition_extension_modules = [SinceExtension]
199
+ # end
200
+ #
201
+ # @dynamic schema_definition_extension_modules, schema_definition_extension_modules=
202
+ attr_accessor :schema_definition_extension_modules
203
+
204
+ # Whether or not to enforce the requirement that the JSON schema version is incremented every time
205
+ # dumping the JSON schemas results in a changed artifact. Defaults to `true`.
206
+ #
207
+ # @note Generally speaking, you will want this to be `true` for any ElasticGraph application that is in
208
+ # production as the versioning of JSON schemas is what supports safe schema evolution as it allows
209
+ # ElasticGraph to identify which version of the JSON schema the publishing system was operating on
210
+ # when it published an event.
211
+ #
212
+ # It can be useful to set it to `false` before your application is in production, as you do not want
213
+ # to be forced to bump the version after every single schema change while you are building an initial
214
+ # prototype.
215
+ #
216
+ # @return [Boolean] whether to require `json_schema_version` to be incremented on changes that impact `json_schemas.yaml`
217
+ # @see SchemaDefinition::API#json_schema_version
218
+ #
219
+ # @example Disable enforcement during initial prototyping
220
+ # ElasticGraph::Local::RakeTasks.new(
221
+ # local_config_yaml: "config/settings/local.yaml",
222
+ # path_to_schema: "config/schema.rb"
223
+ # ) do |tasks|
224
+ # # TODO: remove this once we're past the prototyping stage
225
+ # tasks.enforce_json_schema_version = false
226
+ # end
227
+ #
228
+ # @dynamic enforce_json_schema_version, enforce_json_schema_version=
229
+ attr_accessor :enforce_json_schema_version
230
+
231
+ # List of Elasticsearch versions you want to be able to boot. Rake tasks will be defined for each version to support booting and
232
+ # halting Elasticsearch locally. Defaults to the versions of Elasticsearch that are exercised by the ElasticGraph test suite, as
233
+ # defined by `lib/elastic_graph/local/tested_database_versions.yaml`:
234
+ #
235
+ # {include:file:elasticgraph-local/lib/elastic_graph/local/tested_database_versions.yaml}
236
+ #
237
+ # @return [Array<String>] list of Elasticsearch versions
238
+ # @see #opensearch_versions
239
+ #
240
+ # @example Disable Elasticsearch tasks for a project that uses OpenSearch
241
+ # ElasticGraph::Local::RakeTasks.new(
242
+ # local_config_yaml: "config/settings/local.yaml",
243
+ # path_to_schema: "config/schema.rb"
244
+ # ) do |tasks|
245
+ # tasks.elasticsearch_versions = []
246
+ # end
247
+ #
248
+ # @dynamic elasticsearch_versions, elasticsearch_versions=
249
+ attr_accessor :elasticsearch_versions
250
+
251
+ # List of OpenSearch versions you want to be able to boot. Rake tasks will be defined for each version to support booting and
252
+ # halting OpenSearch locally. Defaults to the versions of OpenSearch that are exercised by the ElasticGraph test suite, as
253
+ # defined by `lib/elastic_graph/local/tested_database_versions.yaml`:
254
+ #
255
+ # {include:file:elasticgraph-local/lib/elastic_graph/local/tested_database_versions.yaml}
256
+ #
257
+ # @return [Array<String>] list of OpenSearch versions
258
+ # @see #elasticsearch_versions
259
+ #
260
+ # @example Disable OpenSearch tasks for a project that uses Elasticsearch
261
+ # ElasticGraph::Local::RakeTasks.new(
262
+ # local_config_yaml: "config/settings/local.yaml",
263
+ # path_to_schema: "config/schema.rb"
264
+ # ) do |tasks|
265
+ # tasks.opensearch_versions = []
266
+ # end
267
+ #
268
+ # @dynamic opensearch_versions, opensearch_versions=
269
+ attr_accessor :opensearch_versions
270
+
271
+ # Hash mapping environments (e.g. `:test`, `:dev`, etc) to port numbers for use when booting Elasticsearch or OpenSearch. The hash
272
+ # automatically includes an entry for the `:local` environment, using a port number extracted from `local_config_yaml`.
273
+ #
274
+ # @note When booting Elasticsearch/OpenSearch, Kibana (or its OpenSearch equivalent, "OpenSearch Dashboards") will also get booted,
275
+ # selecting the port by adding `10000` to the configured port.
276
+ #
277
+ # @return [Hash<Symbol, Integer>] mapping from environment name to port number
278
+ #
279
+ # @example Define what port to use to boot the datastore for the `:test` environment
280
+ # ElasticGraph::Local::RakeTasks.new(
281
+ # local_config_yaml: "config/settings/local.yaml",
282
+ # path_to_schema: "config/schema.rb"
283
+ # ) do |tasks|
284
+ # tasks.env_port_mapping = {test: 9999}
285
+ # end
286
+ #
287
+ # @dynamic env_port_mapping, env_port_mapping=
288
+ attr_accessor :env_port_mapping
289
+
290
+ # IO for printing output (defaults to stdout).
291
+ #
292
+ # @return [IO] IO object used for printing output.
293
+ #
294
+ # @dynamic output, output=
295
+ attr_accessor :output
296
+
297
+ # Maximum time (in seconds) to wait for the datastore to boot when booting it as a daemon. Defaults to 120.
298
+ #
299
+ # @return [Integer] maximum time in seconds to wait when booting Elasticsearch/OpenSearch as a daemon
300
+ #
301
+ # @dynamic daemon_timeout, daemon_timeout=
302
+ attr_accessor :daemon_timeout
303
+
304
+ # Offset we add to a port number for the UI (e.g. Kibana or OpenSearch Dashboards).
305
+ #
306
+ # Example: if Elasticsearch/OpenSearch is running on port 9876, the UI for it will run on port 19876.
307
+ UI_PORT_OFFSET = 10_000
308
+
309
+ # As per https://en.wikipedia.org/wiki/Registered_port, valid user port numbers are 1024 to 49151, but
310
+ # with our UI offset we need to truncate the range further.
311
+ #
312
+ # @private
313
+ VALID_PORT_RANGE = 1024..(49151 - UI_PORT_OFFSET)
314
+
315
+ # Register a callback for use when indexing a batch fake data. An `index_fake_data:[type]` rake task will be generated for each
316
+ # registered callback.
317
+ #
318
+ # @param type [Symbol] type of data batch. Can be the name of a GraphQL type or any other name you want to give a batch of fake data
319
+ # @yield [Array<Hash<String, Object>>, Array<Hash<Symbol, Object>>] list the block should append to when generating data
320
+ # @yieldreturn [void]
321
+ #
322
+ # @example Register a callback to generate fake `campaigns` data
323
+ # ElasticGraph::Local::RakeTasks.new(
324
+ # local_config_yaml: "config/settings/local.yaml",
325
+ # path_to_schema: "config/schema.rb"
326
+ # ) do |tasks|
327
+ # tasks.define_fake_data_batch_for :campaigns do |batch|
328
+ # batch.concat(FactoryBot.build_list(:campaigns))
329
+ # end
330
+ # end
331
+ def define_fake_data_batch_for(type, &block)
332
+ @fake_data_batch_generator_by_type[type] = block
333
+ end
334
+
335
+ # @note This method uses keyword args for all required arguments. Optional task settings are instead specified using the block.
336
+ # @param local_config_yaml [String, Pathname] path to the settings YAML file for the local/development environment
337
+ # @param path_to_schema [String, Pathname] path to the Ruby schema definition file--either the only file that defines the schema
338
+ # (using `ElasticGraph.define_schema`) or the "main" schema definition file, which loads other files which further define parts of
339
+ # the schema.
340
+ # @yield [RakeTasks] instance for further configuration
341
+ # @yieldreturn [void]
342
+ def initialize(local_config_yaml:, path_to_schema:)
343
+ @local_config_yaml = local_config_yaml.to_s
344
+
345
+ self.index_document_sizes = false
346
+ self.schema_element_name_form = :camelCase
347
+ self.schema_element_name_overrides = {}
348
+ self.derived_type_name_formats = {}
349
+ self.type_name_overrides = {}
350
+ self.enum_value_overrides_by_type = {}
351
+ self.schema_definition_extension_modules = []
352
+ self.enforce_json_schema_version = true
353
+ self.env_port_mapping = {}
354
+ self.output = $stdout
355
+ self.daemon_timeout = 120
356
+
357
+ database_versions = ::YAML.load_file("#{__dir__}/tested_database_versions.yaml")
358
+ self.elasticsearch_versions = database_versions.fetch("elasticsearch")
359
+ self.opensearch_versions = database_versions.fetch("opensearch")
360
+
361
+ @fake_data_batch_generator_by_type = {}
362
+
363
+ yield self if block_given?
364
+
365
+ # Default the local port from the local_config_yaml file.
366
+ self.env_port_mapping = {"local" => local_datastore_port}.merge(env_port_mapping || {})
367
+ if (invalid_port_mapping = env_port_mapping.reject { |env, port| VALID_PORT_RANGE.cover?(port) }).any?
368
+ raise "`env_port_mapping` has invalid ports: #{invalid_port_mapping.inspect}. Valid ports must be in the #{VALID_PORT_RANGE} range."
369
+ end
370
+
371
+ # Load admin and schema def rake tasks...
372
+ Admin::RakeTasks.from_yaml_file(local_config_yaml, output: output)
373
+ SchemaDefinition::RakeTasks.new(
374
+ index_document_sizes: index_document_sizes,
375
+ path_to_schema: path_to_schema,
376
+ schema_artifacts_directory: local_config.fetch("schema_artifacts").fetch("directory"),
377
+ schema_element_name_form: schema_element_name_form,
378
+ schema_element_name_overrides: schema_element_name_overrides,
379
+ derived_type_name_formats: derived_type_name_formats,
380
+ type_name_overrides: type_name_overrides,
381
+ enum_value_overrides_by_type: enum_value_overrides_by_type,
382
+ extension_modules: schema_definition_extension_modules,
383
+ enforce_json_schema_version: enforce_json_schema_version,
384
+ output: output
385
+ )
386
+
387
+ # ...then define a bunch of our own.
388
+ define_docker_tasks("Elasticsearch", "Kibana", elasticsearch_versions, /license \[[^\]]+\] mode \[[^\]]+\] - valid/)
389
+ define_docker_tasks("OpenSearch", "OpenSearch Dashboards", opensearch_versions, /o\.o\.n\.Node.+started/)
390
+ define_other_tasks
391
+ end
392
+
393
+ private
394
+
395
+ def define_docker_tasks(description, ui_variant, versions, ready_log_line)
396
+ variant = description.downcase.to_sym
397
+ namespace variant do
398
+ env_port_mapping.each do |env, port|
399
+ namespace env do
400
+ versions.each do |version|
401
+ namespace version do
402
+ define_docker_tasks_for_version(description, variant, ui_variant, port: port, version: version, env: env, ready_log_line: ready_log_line)
403
+ end
404
+ end
405
+
406
+ if (max_version = versions.max_by { |v| Gem::Version.create(v) })
407
+ define_docker_tasks_for_version(description, variant, ui_variant, port: port, version: max_version, env: env, ready_log_line: ready_log_line)
408
+ end
409
+ end
410
+ end
411
+ end
412
+ end
413
+
414
+ def define_docker_tasks_for_version(description, variant, ui_variant, port:, version:, env:, ready_log_line:)
415
+ ui_port = port + UI_PORT_OFFSET
416
+
417
+ docker_runner = DockerRunner.new(
418
+ variant,
419
+ port: port,
420
+ ui_port: port + UI_PORT_OFFSET,
421
+ version: version,
422
+ env: env,
423
+ ready_log_line: ready_log_line,
424
+ output: output,
425
+ daemon_timeout: daemon_timeout
426
+ )
427
+
428
+ desc "Boots #{description} #{version} for the #{env} environment on port #{port} (and #{ui_variant} on port #{ui_port})"
429
+ task(:boot) { docker_runner.boot }
430
+
431
+ desc "Boots #{description} #{version} as a background daemon for the #{env} environment on port #{port} (and #{ui_variant} on port #{ui_port})"
432
+ task(:daemon) do |t|
433
+ docker_runner.boot_as_daemon(halt_command: "rake #{t.name.sub(/:\w+\z/, ":halt")}")
434
+ end
435
+
436
+ desc "Halts the #{description} #{version} daemon for the #{env} environment"
437
+ task(:halt) { docker_runner.halt }
438
+ end
439
+
440
+ def define_other_tasks
441
+ index_fake_data_tasks = @fake_data_batch_generator_by_type.keys.map do |type|
442
+ "index_fake_data:#{type}"
443
+ end
444
+
445
+ database_to_boot =
446
+ if elasticsearch_versions.empty? && opensearch_versions.empty?
447
+ raise "Both `elasticsearch_versions` and `opensearch_versions` are empty, but we need at least one of them to have a version in order to provide the boot tasks."
448
+ elsif elasticsearch_versions.empty?
449
+ "OpenSearch"
450
+ else
451
+ "Elasticsearch"
452
+ end
453
+
454
+ desc "Boots ElasticGraph locally from scratch: boots #{database_to_boot}, configures it, indexes fake data, and boots GraphiQL"
455
+ task :boot_locally, [:port, :rackup_args, :no_open] => ["#{database_to_boot.downcase}:local:daemon", *index_fake_data_tasks, "boot_graphiql"]
456
+
457
+ desc "Boots ElasticGraph locally with the GraphiQL UI, and opens it in a browser."
458
+ task :boot_graphiql, [:port, :rackup_args, :no_open] => :ensure_datastore_ready_for_indexing_and_querying do |task, args|
459
+ args.with_defaults(port: 9393, rackup_args: "", no_open: false)
460
+ port = args.fetch(:port)
461
+
462
+ # :nocov: -- we can't test `open` behavior through a test, and simplecov can't detect coverage in a subprocess.
463
+ unless args.fetch(:no_open)
464
+ fork do
465
+ sleep 3 # give the app a bit of time to boot before we try to open it.
466
+ sh "open http://localhost:#{port}/"
467
+ end
468
+ end
469
+ # :nocov:
470
+
471
+ sh "ELASTICGRAPH_YAML_FILE=#{@local_config_yaml.shellescape} bundle exec rackup #{::File.join(__dir__.to_s, "config.ru").shellescape} --port #{port} #{args.fetch(:rackup_args)}"
472
+ end
473
+
474
+ namespace :index_fake_data do
475
+ @fake_data_batch_generator_by_type.each do |type, generator|
476
+ desc "Indexes num_batches of #{type} fake data into the local datastore"
477
+ task type, [:num_batches] => :ensure_datastore_ready_for_indexing_and_querying do |task, args|
478
+ require "elastic_graph/local/local_indexer"
479
+ args.with_defaults(num_batches: 1)
480
+ LocalIndexer.new(@local_config_yaml, generator, output: output).index_fake_data(Integer(args[:num_batches]))
481
+ end
482
+ end
483
+ end
484
+
485
+ task :ensure_local_datastore_running do
486
+ unless /200 OK/.match?(`curl -is localhost:#{local_datastore_port}`)
487
+ if elasticsearch_versions.empty?
488
+ raise <<~EOS
489
+ OpenSearch is not running locally. You need to start it in another terminal using this command:
490
+
491
+ bundle exec rake opensearch:local:boot
492
+ EOS
493
+ elsif opensearch_versions.empty?
494
+ raise <<~EOS
495
+ Elasticsearch is not running locally. You need to start it in another terminal using this command:
496
+
497
+ bundle exec rake elasticsearch:local:boot
498
+ EOS
499
+ else
500
+ raise <<~EOS
501
+ Neither Elasticsearch nor OpenSearch are running locally. You need to start one of them in another terminal using one of these commands:
502
+
503
+ bundle exec rake elasticsearch:local:boot
504
+ bundle exec rake opensearch:local:boot
505
+ EOS
506
+ end
507
+ end
508
+ end
509
+
510
+ task ensure_datastore_ready_for_indexing_and_querying: [
511
+ :ensure_local_datastore_running,
512
+ "schema_artifacts:dump",
513
+ "clusters:configure:perform"
514
+ ]
515
+
516
+ namespace "clusters:configure" do
517
+ %i[dry_run perform].each do |subtask|
518
+ desc "(after first dumping the schema artifacts)"
519
+ task subtask => [:ensure_local_datastore_running, "schema_artifacts:dump"]
520
+ end
521
+ end
522
+ end
523
+
524
+ def local_datastore_port
525
+ @local_datastore_port ||= local_config
526
+ .fetch("datastore")
527
+ .fetch("clusters")
528
+ .fetch("main")
529
+ .fetch("url")[/localhost:(\d+)$/, 1]
530
+ .then { |port_str| Integer(port_str) }
531
+ end
532
+
533
+ def local_config
534
+ @local_config ||= ::YAML.safe_load_file(@local_config_yaml, aliases: true)
535
+ end
536
+ end
537
+ end
538
+ end
@@ -0,0 +1,8 @@
1
+ # @markup text
2
+ # This file determines the versions the ElasticGraph CI build tests against, and is also
3
+ # used to provide the default versions offered by the `elasticgraph-local` rake tasks.
4
+ elasticsearch:
5
+ - 8.14.1 # latest version as of 2024-07-02.
6
+ opensearch:
7
+ - 2.15.0 # latest version as of 2024-07-02.
8
+ - 2.7.0 # lowest version ElasticGraph currently supports