esse 0.0.5 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (75) hide show
  1. checksums.yaml +4 -4
  2. data/exec/esse +3 -1
  3. data/lib/esse/backend/index/aliases.rb +7 -3
  4. data/lib/esse/backend/index/close.rb +6 -3
  5. data/lib/esse/backend/index/create.rb +19 -8
  6. data/lib/esse/backend/index/delete.rb +13 -8
  7. data/lib/esse/backend/index/documents.rb +2 -2
  8. data/lib/esse/backend/index/existance.rb +1 -1
  9. data/lib/esse/backend/index/open.rb +6 -3
  10. data/lib/esse/backend/index/refresh.rb +2 -2
  11. data/lib/esse/backend/index/reset.rb +2 -4
  12. data/lib/esse/backend/index/update.rb +37 -12
  13. data/lib/esse/backend/index.rb +5 -1
  14. data/lib/esse/backend/index_type/documents.rb +7 -7
  15. data/lib/esse/cli/event_listener.rb +87 -0
  16. data/lib/esse/cli/generate.rb +7 -3
  17. data/lib/esse/cli/index/base_operation.rb +76 -0
  18. data/lib/esse/cli/index/close.rb +26 -0
  19. data/lib/esse/cli/index/create.rb +26 -0
  20. data/lib/esse/cli/index/delete.rb +26 -0
  21. data/lib/esse/cli/index/open.rb +26 -0
  22. data/lib/esse/cli/index/reset.rb +26 -0
  23. data/lib/esse/cli/index/update_aliases.rb +32 -0
  24. data/lib/esse/cli/index/update_mapping.rb +33 -0
  25. data/lib/esse/cli/index/update_settings.rb +26 -0
  26. data/lib/esse/cli/index.rb +70 -2
  27. data/lib/esse/cli/templates/config.rb.erb +20 -0
  28. data/lib/esse/cli/templates/index.rb.erb +74 -9
  29. data/lib/esse/cli/templates/type_collection.rb.erb +41 -0
  30. data/lib/esse/cli/templates/{mappings.json → type_mappings.json} +0 -0
  31. data/lib/esse/cli/templates/{serializer.rb.erb → type_serializer.rb.erb} +9 -4
  32. data/lib/esse/cli.rb +75 -3
  33. data/lib/esse/cluster.rb +18 -2
  34. data/lib/esse/config.rb +39 -5
  35. data/lib/esse/core.rb +15 -33
  36. data/lib/esse/errors.rb +47 -0
  37. data/lib/esse/events/bus.rb +103 -0
  38. data/lib/esse/events/event.rb +64 -0
  39. data/lib/esse/events/publisher.rb +119 -0
  40. data/lib/esse/events.rb +49 -0
  41. data/lib/esse/index/backend.rb +2 -1
  42. data/lib/esse/index/base.rb +4 -6
  43. data/lib/esse/index/mappings.rb +2 -3
  44. data/lib/esse/index/settings.rb +7 -8
  45. data/lib/esse/index.rb +2 -1
  46. data/lib/esse/index_mapping.rb +2 -2
  47. data/lib/esse/index_setting.rb +8 -4
  48. data/lib/esse/index_type/actions.rb +2 -1
  49. data/lib/esse/index_type/backend.rb +2 -1
  50. data/lib/esse/index_type/mappings.rb +2 -2
  51. data/lib/esse/index_type.rb +6 -1
  52. data/lib/esse/logging.rb +19 -0
  53. data/lib/esse/object_document_mapper.rb +96 -0
  54. data/lib/esse/primitives/hash_utils.rb +29 -0
  55. data/lib/esse/primitives/hstring.rb +4 -3
  56. data/lib/esse/primitives/output.rb +64 -0
  57. data/lib/esse/primitives.rb +1 -0
  58. data/lib/esse/template_loader.rb +1 -1
  59. data/lib/esse/version.rb +1 -1
  60. data/lib/esse.rb +12 -3
  61. metadata +106 -22
  62. data/.gitignore +0 -12
  63. data/.rubocop.yml +0 -128
  64. data/.tool-versions +0 -1
  65. data/.yardopts +0 -2
  66. data/CHANGELOG.md +0 -0
  67. data/Gemfile +0 -7
  68. data/Gemfile.lock +0 -62
  69. data/LICENSE.txt +0 -21
  70. data/README.md +0 -52
  71. data/Rakefile +0 -4
  72. data/bin/console +0 -22
  73. data/bin/setup +0 -8
  74. data/esse.gemspec +0 -40
  75. data/lib/esse/index_type/serializer.rb +0 -87
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base_operation'
4
+
5
+ module Esse
6
+ module CLI
7
+ class Index::Create < Index::BaseOperation
8
+ def run
9
+ validate_options!
10
+ indices.each do |index|
11
+ index.elasticsearch.create_index!(**options)
12
+ end
13
+ end
14
+
15
+ private
16
+
17
+ def options
18
+ @options.slice(*@options.keys - CLI_IGNORE_OPTS)
19
+ end
20
+
21
+ def validate_options!
22
+ validate_indices_option!
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base_operation'
4
+
5
+ module Esse
6
+ module CLI
7
+ class Index::Delete < Index::BaseOperation
8
+ def run
9
+ validate_options!
10
+ indices.each do |index|
11
+ index.elasticsearch.delete_index!(**options)
12
+ end
13
+ end
14
+
15
+ private
16
+
17
+ def options
18
+ @options.slice(*@options.keys - CLI_IGNORE_OPTS)
19
+ end
20
+
21
+ def validate_options!
22
+ validate_indices_option!
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base_operation'
4
+
5
+ module Esse
6
+ module CLI
7
+ class Index::Open < Index::BaseOperation
8
+ def run
9
+ validate_options!
10
+ indices.each do |index|
11
+ index.elasticsearch.open!(**options)
12
+ end
13
+ end
14
+
15
+ private
16
+
17
+ def options
18
+ @options.slice(*@options.keys - CLI_IGNORE_OPTS)
19
+ end
20
+
21
+ def validate_options!
22
+ validate_indices_option!
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base_operation'
4
+
5
+ module Esse
6
+ module CLI
7
+ class Index::Reset < Index::BaseOperation
8
+ def run
9
+ validate_options!
10
+ indices.each do |index|
11
+ index.elasticsearch.reset_index!(**options)
12
+ end
13
+ end
14
+
15
+ private
16
+
17
+ def options
18
+ @options.slice(*@options.keys - CLI_IGNORE_OPTS)
19
+ end
20
+
21
+ def validate_options!
22
+ validate_indices_option!
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base_operation'
4
+
5
+ module Esse
6
+ module CLI
7
+ class Index::UpdateAliases < Index::BaseOperation
8
+ def run
9
+ validate_options!
10
+ indices.each do |index|
11
+ index.elasticsearch.update_aliases!(**options)
12
+ end
13
+ end
14
+
15
+ private
16
+
17
+ def options
18
+ @options.slice(*@options.keys - CLI_IGNORE_OPTS)
19
+ end
20
+
21
+ def validate_options!
22
+ validate_indices_option!
23
+
24
+ if @options[:suffix].nil?
25
+ raise InvalidOption.new(<<~END)
26
+ You must specify a suffix to update the aliases.
27
+ END
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base_operation'
4
+
5
+ module Esse
6
+ module CLI
7
+ class Index::UpdateMapping < Index::BaseOperation
8
+ def run
9
+ validate_options!
10
+ indices.each do |index|
11
+ if index.type_hash.any?
12
+ index.type_hash.each_value do |type|
13
+ # @idea Add update_mapping! to IndexType and use it here
14
+ index.elasticsearch.update_mapping!(type: type.type_name, **options)
15
+ end
16
+ else
17
+ index.elasticsearch.update_mapping!(**options)
18
+ end
19
+ end
20
+ end
21
+
22
+ private
23
+
24
+ def options
25
+ @options.slice(*@options.keys - CLI_IGNORE_OPTS)
26
+ end
27
+
28
+ def validate_options!
29
+ validate_indices_option!
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'base_operation'
4
+
5
+ module Esse
6
+ module CLI
7
+ class Index::UpdateSettings < Index::BaseOperation
8
+ def run
9
+ validate_options!
10
+ indices.each do |index|
11
+ index.elasticsearch.update_settings!(**options)
12
+ end
13
+ end
14
+
15
+ private
16
+
17
+ def options
18
+ @options.slice(*@options.keys - CLI_IGNORE_OPTS)
19
+ end
20
+
21
+ def validate_options!
22
+ validate_indices_option!
23
+ end
24
+ end
25
+ end
26
+ end
@@ -2,12 +2,80 @@
2
2
 
3
3
  require 'thor'
4
4
  require_relative 'base'
5
+
5
6
  module Esse
6
7
  module CLI
7
8
  class Index < Base
8
- desc 'create *INDEX_CLASSES', 'Creates a new index'
9
+ desc 'reset *INDEX_CLASSES', 'Performs zero-downtime index resetting.'
10
+ long_desc <<-DESC
11
+ This task is used to rebuild index with zero-downtime. The task will:
12
+ * Creates a new index using the suffix defined on index class or from CLI.
13
+ * Import documents using the Index collection.
14
+ * Update alias to point to the new index.
15
+ * Delete the old index.
16
+ DESC
17
+ option :suffix, type: :string, default: nil, aliases: '-s', desc: 'Suffix to append to index name'
18
+ def reset(*index_classes)
19
+ require_relative 'index/reset'
20
+ Reset.new(indices: index_classes, **options.to_h.transform_keys(&:to_sym)).run
21
+ end
22
+
23
+ desc 'create *INDEX_CLASSES', 'Creates indices for the given classes'
24
+ long_desc <<-DESC
25
+ Creates index and applies mapping and settings for the given classes.
26
+
27
+ Indices are created with the following naming convention:
28
+ <cluster.index_prefix>_<index_class.index_name>_<index_class.index_version>.
29
+ DESC
30
+ option :suffix, type: :string, default: nil, aliases: '-s', desc: 'Suffix to append to index name'
31
+ option :alias, type: :boolean, default: false, aliases: '-a', desc: 'Update alias after create index'
9
32
  def create(*index_classes)
10
- # Add action here
33
+ require_relative 'index/create'
34
+ Create.new(indices: index_classes, **options.to_h.transform_keys(&:to_sym)).run
35
+ end
36
+
37
+ desc 'delete *INDEX_CLASSES', 'Deletes indices for the given classes'
38
+ option :suffix, type: :string, default: nil, aliases: '-s', desc: 'Suffix to append to index name'
39
+ def delete(*index_classes)
40
+ require_relative 'index/delete'
41
+ Delete.new(indices: index_classes, **options.to_h.transform_keys(&:to_sym)).run
42
+ end
43
+
44
+ desc 'update_aliases *INDEX_CLASS', 'Replaces all existing aliases by the given suffix'
45
+ option :suffix, type: :string, aliases: '-s', desc: 'Suffix to append to index name'
46
+ def update_aliases(*index_classes)
47
+ require_relative 'index/update_aliases'
48
+ UpdateAliases.new(indices: index_classes, **options.to_h.transform_keys(&:to_sym)).run
49
+ end
50
+
51
+ desc 'update_settings *INDEX_CLASS', 'Closes the index for read/write operations, updates the index settings, and open it again'
52
+ option :suffix, type: :string, default: nil, aliases: '-s', desc: 'Suffix to append to index name'
53
+ option :type, type: :string, default: nil, aliases: '-t', desc: 'Document Type to update mapping for'
54
+ def update_settings(*index_classes)
55
+ require_relative 'index/update_settings'
56
+ UpdateSettings.new(indices: index_classes, **options.to_h.transform_keys(&:to_sym)).run
57
+ end
58
+
59
+ desc 'update_mapping *INDEX_CLASS', 'Create or update a mapping'
60
+ option :suffix, type: :string, default: nil, aliases: '-s', desc: 'Suffix to append to index name'
61
+ option :type, type: :string, default: nil, aliases: '-t', desc: 'Document Type to update mapping for'
62
+ def update_mapping(*index_classes)
63
+ require_relative 'index/update_mapping'
64
+ UpdateMapping.new(indices: index_classes, **options.to_h.transform_keys(&:to_sym)).run
65
+ end
66
+
67
+ desc 'close *INDEX_CLASS', 'Close an index (keep the data on disk, but deny operations with the index).'
68
+ option :suffix, type: :string, default: nil, aliases: '-s', desc: 'Suffix to append to index name'
69
+ def close(*index_classes)
70
+ require_relative 'index/close'
71
+ Close.new(indices: index_classes, **options.to_h.transform_keys(&:to_sym)).run
72
+ end
73
+
74
+ desc 'open *INDEX_CLASS', 'Open a previously closed index.'
75
+ option :suffix, type: :string, default: nil, aliases: '-s', desc: 'Suffix to append to index name'
76
+ def open(*index_classes)
77
+ require_relative 'index/open'
78
+ Open.new(indices: index_classes, **options.to_h.transform_keys(&:to_sym)).run
11
79
  end
12
80
  end
13
81
  end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ Esse.configure do |config|
4
+ config.indices_directory = File.expand_path('../../app/indices', __FILE__)
5
+ config.cluster(:default) do |cluster|
6
+ # Default index prefix
7
+ cluster.index_prefix = "esse"
8
+ # you also can add environment to the index prefix if available:
9
+ # cluster.index_prefix = "esse_#{ENV['RACK_ENV']}"
10
+ #
11
+ # Initialize the ElastiSearch client to be used by the default cluster
12
+ cluster.client = Elasticsearch::Client.new
13
+
14
+ # Global index settings
15
+ # cluster.index_settings = {
16
+ # number_of_shards: 5,
17
+ # number_of_replicas: 0,
18
+ # }
19
+ end
20
+ end
@@ -1,11 +1,75 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ <%- @types.each do |type| -%>
4
+ require_relative '<%= @index_name.demodulize.underscore.to_s %>/collections/<%= type.underscore %>_collection'
5
+ <%- end -%>
6
+ <%- @types.each do |type| -%>
7
+ require_relative '<%= @index_name.demodulize.underscore.to_s %>/serializers/<%= type.underscore %>_serializer'
8
+ <%- end -%>
9
+
3
10
  class <%= @index_name %> < <%= @base_class %>
4
11
  # plugin :active_record
5
12
  # plugin :sequel
6
13
 
14
+ <%- if @types.empty? -%>
15
+ # Collection
16
+ # ==========
17
+ #
18
+ # Collection is the source of data for the index. We hightly recommend using a
19
+ # another class that implements the Enumerable interface to better split the
20
+ # responsabilities and easy to test.
21
+ #
22
+ # collection Collections::DocumentCollection
23
+ #
24
+ # but you can also use block definition style:
25
+ # collection do |_context, &block|
26
+ # block.call [{ title: 'foo' }, { title: 'bar' }], context
27
+ # end
28
+ #
29
+ # Serializer block or class yielder should be called with an array of objects.
30
+ # Each these objects should be serialized using the serializer in described in the next section.
31
+ # The number of objects will be indexed to elasticsearch using the bulk api. So adjust the
32
+ # number of objects to be indexed accordingly.
33
+ #
34
+ # Here is a good place to eager loading data from database or any other repository.
35
+ # The bellow example is a rails like application that could preload using activerecord
36
+ #
37
+ # collection do |**context, &block|
38
+ # query = <%= @index_name.camelize %>.all
39
+ # query = query.where(**context[:conditions]) if context[:conditions]
40
+ # query = query.includes(:user) if context[:include_user]
41
+ # query.find_in_batches(batch_size: 5000) do |batch|
42
+ # block.call batch, context
43
+ # end
44
+ # end
45
+
46
+
47
+ # Serializer
48
+ # ==========
49
+ #
50
+ # Serializer is the class responsible for serializing indexing documents.
51
+ # Each object yielded by the collection will be serialized on this step.
52
+ # We recommend using a another class to handle the serialization. The only requirement is
53
+ # that the class implements the `#to_h` method.
54
+ # The serializer class may also be initialized with context if collection block is called with that extra parameter.
55
+ # Ability to call serializer with context is useful for preloading data from database or any other repository.
56
+ #
57
+ # serializer Serializers::DocumentSerializer
58
+ #
59
+ # You can also serialize the collection entry using a block:
60
+ #
61
+ # serializer do |model, context|
62
+ # hash = {
63
+ # name: <%= @index_name.underscore %>.name,
64
+ # }
65
+ # # Context is just an example here. But it's useful for eager loading data.
66
+ # # I'll think a better example when implement this idea.
67
+ # hash[:some_attribute] = <%= @index_name.underscore %>.some_attribute if context[:include_some_attribute]
68
+ # hash
69
+ # end
70
+ <%- end -%>
7
71
  <%- @types.each do |type| -%>
8
- define_type :<%= type %> do
72
+ define_type :<%= type.underscore %> do
9
73
  # Collection
10
74
  # ==========
11
75
  #
@@ -14,19 +78,19 @@ class <%= @index_name %> < <%= @base_class %>
14
78
  # Useful for eager loading data from database or any other repository. Below is an example of a rails like
15
79
  # application could load using activerecord.
16
80
  #
17
- # collection do |conditions, &block|
18
- # context = {}
19
- # <%= type.camelize %>.where(conditions).find_in_batches(batch_size: 5000) do |batch|
20
- # block.call batch, context, ...
81
+ # collection do |context, &block|
82
+ # <%= type.camelize %>.where(context[:conditions]).find_in_batches(batch_size: 5000) do |batch|
83
+ # block.call batch, context
21
84
  # end
22
85
  # end
86
+ collection Collections::<%= type.camelize %>Collection
23
87
  #
24
88
  #
25
89
  # Serializer
26
90
  # ==========
27
91
  #
28
- # The serializer can be any class that respond with the `as_json` class method.
29
- # And the result of its as_json is a Hash.
92
+ # The serializer can be any class that respond with the `to_h` class method.
93
+ # And the result of its to_h is a Hash.
30
94
  #
31
95
  # Here is an example of a simple serializer:
32
96
  # app/serializers/<%= type %>_serializer.rb
@@ -35,7 +99,7 @@ class <%= @index_name %> < <%= @base_class %>
35
99
  # @<%= type %> = <%= type %>
36
100
  # end
37
101
  #
38
- # def as_json
102
+ # def to_h
39
103
  # { '_id' => @<%= type %>.id, 'name' => @<%= type %>.name }
40
104
  # end
41
105
  # end
@@ -45,7 +109,7 @@ class <%= @index_name %> < <%= @base_class %>
45
109
  #
46
110
  # You can also serialize the collection entry using a block:
47
111
  #
48
- # serializer do |model, context = {}|
112
+ # serializer do |model, **context|
49
113
  # hash = {
50
114
  # name: <%= type %>.name,
51
115
  # }
@@ -54,6 +118,7 @@ class <%= @index_name %> < <%= @base_class %>
54
118
  # hash[:some_attribute] = <%= type %>.some_attribute if context[:include_some_attribute]
55
119
  # hash
56
120
  # end
121
+ serializer Serializers::<%= type.camelize %>Serializer
57
122
  end
58
123
  <%- end -%>
59
124
  end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ class <%= @index_name %> < <%= @base_class %>
4
+ module Collections
5
+ class <%= @type.camelize %>Collection
6
+ include Enumerable
7
+
8
+ # @param params [Hash] List of parameters
9
+ def initialize(**params)
10
+ @params = params
11
+ end
12
+
13
+ # Find all <%= @type %> in batches
14
+ #
15
+ # @yield [Array<<%= @type.camelize %>>]
16
+ # @see <%= @index_name %>::<%= @type.camelize %>#collection
17
+ def each
18
+ offset = 0
19
+ while (rows = find_all(offset))
20
+ break if rows.none?
21
+
22
+ # You may preload associations before serialize them
23
+ # associations = preload_associations!(rows)
24
+ # yield(row, associations)
25
+ offset += 1
26
+
27
+ yield(rows, **params)
28
+ end
29
+
30
+ protected
31
+
32
+ attr_reader :params
33
+
34
+ # @param offset [Number] Offset to start from
35
+ def find_all(offset)
36
+ # @TODO load data from persistent store
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -3,16 +3,21 @@
3
3
  class <%= @index_name %> < <%= @base_class %>
4
4
  module Serializers
5
5
  class <%= @type.camelize %>Serializer
6
- def initialize(<%= @type %>, *_other)
6
+ def initialize(<%= @type %>, **params)
7
7
  @entity = <%= @type %>
8
+ @params = params
8
9
  end
9
10
 
10
- def as_json
11
+ def to_h
11
12
  {
12
- id: @entity.id, # This field is required
13
- name: @entity.name,
13
+ id: entity.id, # This field is required
14
+ name: entity.name,
14
15
  }
15
16
  end
17
+
18
+ protected
19
+
20
+ attr_reader :entity, :params
16
21
  end
17
22
  end
18
23
  end
data/lib/esse/cli.rb CHANGED
@@ -2,16 +2,48 @@
2
2
 
3
3
  require 'thor'
4
4
 
5
+ require_relative 'primitives/output'
5
6
  require_relative 'cli/index'
6
7
  require_relative 'cli/generate'
8
+ require_relative 'cli/event_listener'
7
9
 
8
10
  module Esse
9
11
  module CLI
10
- def self.start(*args)
11
- Root.start(*args)
12
+ class << self
13
+ def start(*args)
14
+ Root.start(*args)
15
+ end
16
+
17
+ def with_friendly_errors
18
+ yield
19
+ rescue CLI::Error => e
20
+ Output.print_error(e)
21
+ exit(1)
22
+ end
12
23
  end
13
24
 
14
25
  class Root < Thor
26
+ include Thor::Actions
27
+
28
+ class_option :require, type: :string, aliases: '-r', required: false,
29
+ default: nil, desc: 'Require config file where the application is defined'
30
+ class_option :silent, type: :boolean, aliases: '-s', required: false, default: false, desc: 'Silent mode'
31
+
32
+ def initialize(*)
33
+ super
34
+
35
+ load_app_config(options[:require])
36
+ setup_listeners if !options[:silent] && Esse.config.cli_event_listeners?
37
+ end
38
+
39
+ def self.source_root
40
+ File.expand_path('../cli', __FILE__)
41
+ end
42
+
43
+ def self.exit_on_failure?
44
+ true
45
+ end
46
+
15
47
  map %w[--version -v] => :version
16
48
 
17
49
  desc 'index SUBCOMMAND ...ARGS', 'Manage indices'
@@ -22,7 +54,47 @@ module Esse
22
54
 
23
55
  desc '--version, -v', 'Show package version'
24
56
  def version
25
- puts format('Esse version: %<version>s', version: Esse::VERSION)
57
+ Output.print_message('Esse version: %<version>s', version: Esse::VERSION)
58
+ end
59
+
60
+ desc 'install', 'Generate boilerplate configuration files'
61
+ option :path, type: :string, aliases: '-p', required: false, default: './'
62
+ def install
63
+ path = Pathname.new(File.expand_path(options[:path], Dir.pwd))
64
+ path = path.dirname unless path.directory?
65
+ @app_dir = path.basename
66
+ template(
67
+ 'templates/config.rb.erb',
68
+ path.join('config/esse.rb'),
69
+ )
70
+ end
71
+
72
+ private
73
+
74
+ def setup_listeners
75
+ Esse::Events.__bus__.events.keys.grep(/^elasticsearch/).each do |event_name|
76
+ Esse::Events.subscribe(event_name) do |event|
77
+ EventListener[event_name]&.call(event)
78
+ end
79
+ end
80
+ end
81
+
82
+ def load_app_config(path)
83
+ if path.nil?
84
+ CLI_CONFIG_PATHS.each do |config_path|
85
+ next unless File.exist?(config_path)
86
+ path = config_path
87
+ break
88
+ end
89
+ end
90
+ return unless path
91
+
92
+ begin
93
+ Output.print_message('Loading configuration file: %<path>s', path: path)
94
+ load path
95
+ rescue LoadError => e
96
+ raise InvalidOption, e.message
97
+ end
26
98
  end
27
99
  end
28
100
  end
data/lib/esse/cluster.rb CHANGED
@@ -2,7 +2,8 @@
2
2
 
3
3
  module Esse
4
4
  class Cluster
5
- ATTRIBUTES = %i[index_prefix index_settings client].freeze
5
+ ATTRIBUTES = %i[index_prefix index_settings client wait_for_status].freeze
6
+ WAIT_FOR_STATUSES = %w[green yellow red].freeze
6
7
 
7
8
  # The index prefix. For example an index named UsersIndex.
8
9
  # With `index_prefix = 'app1'`. Final index/alias is: 'app1_users'
@@ -11,6 +12,15 @@ module Esse
11
12
  # This settings will be passed through all indices during the mapping
12
13
  attr_accessor :index_settings
13
14
 
15
+ # if this option set, actions such as creating or deleting index,
16
+ # importing data will wait for the status specified. Extremely useful
17
+ # for tests under heavy indices manipulations.
18
+ # Value can be set to `red`, `yellow` or `green`.
19
+ #
20
+ # Example:
21
+ # wait_for_status: green
22
+ attr_accessor :wait_for_status
23
+
14
24
  attr_reader :id
15
25
 
16
26
  def initialize(id:, **options)
@@ -39,7 +49,7 @@ module Esse
39
49
  # with the settings that will be used to initialize Elasticsearch::Client
40
50
  def client=(es_client)
41
51
  @client = if es_client.is_a?(Hash)
42
- settings = es_client.each_with_object({}) { |(k,v), r| r[k.to_sym] = v }
52
+ settings = es_client.each_with_object({}) { |(k, v), r| r[k.to_sym] = v }
43
53
  Elasticsearch::Client.new(settings)
44
54
  else
45
55
  es_client
@@ -54,5 +64,11 @@ module Esse
54
64
  attrs << format('client=%p', @client)
55
65
  format('#<Esse::Cluster %<attrs>s>', attrs: attrs.join(' '))
56
66
  end
67
+
68
+ def wait_for_status!(status: wait_for_status)
69
+ return unless WAIT_FOR_STATUSES.include?(status.to_s)
70
+
71
+ client.cluster.health(wait_for_status: status.to_s)
72
+ end
57
73
  end
58
74
  end