Skip to content

Commit

Permalink
chore: add --settings to the reset, create and update_settings index …
Browse files Browse the repository at this point in the history
…cli (#20)
  • Loading branch information
marcosgz authored Aug 2, 2024
1 parent 7d8fef8 commit e370f09
Show file tree
Hide file tree
Showing 40 changed files with 258 additions and 96 deletions.
16 changes: 11 additions & 5 deletions lib/esse/cli/index.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,11 @@ class Index < Base
option :suffix, type: :string, default: nil, aliases: '-s', desc: 'Suffix to append to index name'
option :import, type: :boolean, default: true, desc: 'Import documents before point alias to the new index'
option :optimize, type: :boolean, default: true, desc: 'Optimize index before import documents by disabling refresh_interval and setting number_of_replicas to 0'
option :settings, type: :hash, default: nil, desc: 'List of settings to pass to the index class. Example: --settings=refresh_interval:1s,number_of_replicas:0'
def reset(*index_classes)
require_relative 'index/reset'
Reset.new(indices: index_classes, **options.to_h.transform_keys(&:to_sym)).run
opts = HashUtils.deep_transform_keys(options.to_h, &:to_sym)
Reset.new(indices: index_classes, **opts).run
end

# @TODO Add reindex task to create a new index and import documents from the old index using _reindex API
Expand All @@ -33,9 +35,11 @@ def reset(*index_classes)
DESC
option :suffix, type: :string, default: nil, aliases: '-s', desc: 'Suffix to append to index name'
option :alias, type: :boolean, default: false, aliases: '-a', desc: 'Update alias after create index'
option :settings, type: :hash, default: nil, desc: 'List of settings to pass to the index class. Example: --settings=index.refresh_interval:-1,index.number_of_replicas:0'
def create(*index_classes)
require_relative 'index/create'
Create.new(indices: index_classes, **options.to_h.transform_keys(&:to_sym)).run
opts = HashUtils.deep_transform_keys(options.to_h, &:to_sym)
Create.new(indices: index_classes, **opts).run
end

desc 'delete *INDEX_CLASSES', 'Deletes indices for the given classes'
Expand All @@ -58,9 +62,11 @@ def update_aliases(*index_classes)
desc 'update_settings *INDEX_CLASS', 'Closes the index for read/write operations, updates the index settings, and open it again'
option :suffix, type: :string, default: nil, aliases: '-s', desc: 'Suffix to append to index name'
option :type, type: :string, default: nil, aliases: '-t', desc: 'Document Type to update mapping for'
option :settings, type: :hash, default: nil, desc: 'List of settings to pass to the index class. Example: --settings=index.refresh_interval:-1,index.number_of_replicas:0'
def update_settings(*index_classes)
require_relative 'index/update_settings'
UpdateSettings.new(indices: index_classes, **options.to_h.transform_keys(&:to_sym)).run
opts = HashUtils.deep_transform_keys(options.to_h, &:to_sym)
UpdateSettings.new(indices: index_classes, **opts).run
end

desc 'update_mapping *INDEX_CLASS', 'Create or update a mapping'
Expand Down Expand Up @@ -89,8 +95,8 @@ def open(*index_classes)
option :suffix, type: :string, default: nil, aliases: '-s', desc: 'Suffix to append to index name'
option :context, type: :hash, default: {}, required: true, desc: 'List of options to pass to the index class'
option :repo, type: :string, default: nil, alias: '-r', desc: 'Repository to use for import'
option :eager_include_document_attributes, type: :string, default: nil, desc: 'Comma separated list of lazy document attributes to include to the bulk index request'
option :lazy_update_document_attributes, type: :string, default: nil, desc: 'Comma separated list of lazy document attributes to bulk update after the bulk index request'
option :eager_include_document_attributes, type: :string, default: nil, desc: 'Comma separated list of lazy document attributes to include to the bulk index request. Or pass `true` to include all lazy attributes'
option :lazy_update_document_attributes, type: :string, default: nil, desc: 'Comma separated list of lazy document attributes to bulk update after the bulk index request Or pass `true` to include all lazy attributes'
def import(*index_classes)
require_relative 'index/import'
opts = HashUtils.deep_transform_keys(options.to_h, &:to_sym)
Expand Down
6 changes: 4 additions & 2 deletions lib/esse/cli/templates/config.rb.erb
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,10 @@ Esse.configure do |config|

# Global index settings
# cluster.settings = {
# number_of_shards: 5,
# number_of_replicas: 0,
# index: {
# number_of_shards: 5,
# number_of_replicas: 0,
# }
# }

# Global index mappings
Expand Down
7 changes: 5 additions & 2 deletions lib/esse/config.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,11 @@ module Esse
# cluster.index_prefix = 'backend'
# cluster.client = Elasticsearch::Client.new
# cluster.settings = {
# number_of_shards: 2,
# number_of_replicas: 0
# index: {
# number_of_shards: 2,
# number_of_replicas: 1
# },
# analysis: { ... }
# }
# cluster.mappings = {
# dynamic_templates: [...]
Expand Down
25 changes: 13 additions & 12 deletions lib/esse/index/indices.rb
Original file line number Diff line number Diff line change
Expand Up @@ -26,10 +26,10 @@ module ClassMethods
#
# @see http://www.elasticsearch.org/blog/changing-mapping-with-zero-downtime/
# @see Esse::Transport#create_index
def create_index(suffix: nil, body: nil, **options)
def create_index(suffix: nil, body: nil, settings: nil, **options)
options = CREATE_INDEX_RESERVED_KEYWORDS.merge(options)
name = build_real_index_name(suffix)
definition = body || [settings_hash, mappings_hash].reduce(&:merge)
definition = body || [settings_hash(settings: settings), mappings_hash].reduce(&:merge)

if options.delete(:alias) && name != index_name
definition[:aliases] = { index_name => {} }
Expand All @@ -48,21 +48,21 @@ def create_index(suffix: nil, body: nil, **options)
# @return [Hash] the elasticsearch response
#
# @see https://www.elastic.co/guide/en/elasticsearch/reference/master/indices-open-close.html
def reset_index(suffix: index_suffix, optimize: true, import: true, reindex: false, **options)
def reset_index(suffix: index_suffix, settings: nil, optimize: true, import: true, reindex: false, **options)
cluster.throw_error_when_readonly!

suffix ||= Esse.timestamp
suffix = Esse.timestamp while index_exist?(suffix: suffix)

if optimize && import
definition = [settings_hash, mappings_hash].reduce(&:merge)
definition = [settings_hash(settings: settings), mappings_hash].reduce(&:merge)
number_of_replicas = definition.dig(Esse::SETTING_ROOT_KEY, :index, :number_of_replicas)
refresh_interval = definition.dig(Esse::SETTING_ROOT_KEY, :index, :refresh_interval)
new_number_of_replicas = ((definition[Esse::SETTING_ROOT_KEY] ||= {})[:index] ||= {})[:number_of_replicas] = 0
new_refresh_interval = ((definition[Esse::SETTING_ROOT_KEY] ||= {})[:index] ||= {})[:refresh_interval] = '-1'
create_index(**options, suffix: suffix, alias: false, body: definition)
else
create_index(**options, suffix: suffix, alias: false)
create_index(**options, suffix: suffix, alias: false, settings: settings)
end

if index_exist? && aliases.none?
Expand All @@ -75,7 +75,7 @@ def reset_index(suffix: index_suffix, optimize: true, import: true, reindex: fal
end

if optimize && import && number_of_replicas != new_number_of_replicas || refresh_interval != new_refresh_interval
update_settings(suffix: suffix)
update_settings(suffix: suffix, settings: settings)
refresh(suffix: suffix)
end

Expand Down Expand Up @@ -152,16 +152,17 @@ def update_mapping(suffix: nil, **options)
#
# @param :suffix [String, nil] :suffix The index suffix
# @see Esse::Transport#update_settings
def update_settings(suffix: nil, **options)
def update_settings(suffix: nil, settings: nil, **options)
response = nil

settings = HashUtils.deep_transform_keys(settings_hash.fetch(Esse::SETTING_ROOT_KEY), &:to_s)
settings = HashUtils.deep_transform_keys(settings_hash(settings: settings).fetch(Esse::SETTING_ROOT_KEY), &:to_sym)
if options[:body]
settings = settings.merge(HashUtils.deep_transform_keys(options.delete(:body), &:to_s))
body = HashUtils.deep_transform_keys(options.delete(:body), &:to_sym)
settings = HashUtils.deep_merge(settings, body)
end
settings.delete('number_of_shards') # Can't change number of shards for an index
settings['index']&.delete('number_of_shards')
analysis = settings.delete('analysis')
settings.delete(:number_of_shards) # Can't change number of shards for an index
settings[:index]&.delete(:number_of_shards)
analysis = settings.delete(:analysis)

if settings.any?
response = cluster.api.update_settings(index: index_name(suffix: suffix), body: settings, **options)
Expand Down
25 changes: 22 additions & 3 deletions lib/esse/index/settings.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,28 @@ module Esse
# https://github.com/elastic/elasticsearch-ruby/blob/master/elasticsearch-api/lib/elasticsearch/api/actions/indices/put_settings.rb
class Index
module ClassMethods
def settings_hash
# Elasticsearch supports passing index.* related settings directly in the body of the request.
# We are moving it to the index key to make it more explicit and to be the source-of-truth when merging settings.
# So the settings `{ number_of_shards: 1 }` will be transformed to `{ index: { number_of_shards: 1 } }`
INDEX_SIMPLIFIED_SETTINGS = %i[
number_of_shards
number_of_replicas
refresh_interval
].freeze

def settings_hash(settings: nil)
hash = setting.body
{ Esse::SETTING_ROOT_KEY => (hash.key?(Esse::SETTING_ROOT_KEY) ? hash[Esse::SETTING_ROOT_KEY] : hash) }
values = (hash.key?(Esse::SETTING_ROOT_KEY) ? hash[Esse::SETTING_ROOT_KEY] : hash)
values = HashUtils.explode_keys(values)
if settings.is_a?(Hash)
values = HashUtils.deep_merge(values, HashUtils.explode_keys(settings))
end
INDEX_SIMPLIFIED_SETTINGS.each do |key|
next unless values.key?(key)

(values[:index] ||= {}).merge!(key => values.delete(key))
end
{ Esse::SETTING_ROOT_KEY => values }
end

# Define /_settings definition by each index.
Expand All @@ -18,7 +37,7 @@ def settings_hash
#
# class UserIndex < Esse::Index
# settings {
# number_of_replicas: 4,
# index: { number_of_replicas: 4 }
# }
# end
#
Expand Down
10 changes: 10 additions & 0 deletions lib/esse/primitives/hash_utils.rb
Original file line number Diff line number Diff line change
Expand Up @@ -47,5 +47,15 @@ def deep_merge!(target, source)
end
end
end

def explode_keys(hash, separator = '.')
hash.each_with_object({}) do |(key, value), result|
is_symbol = key.is_a?(Symbol)
keys = key.to_s.split(separator)
last_key = keys.pop
current = keys.reduce(result) { |memo, k| memo[is_symbol ? k.to_sym : k] ||= {} }
current[is_symbol ? last_key.to_sym : last_key] = value
end
end
end
end
5 changes: 5 additions & 0 deletions spec/esse/cli/index/create_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,11 @@
expect(CitiesIndex).to receive(:create_index).with(alias: false).and_return(true)
cli_exec(%w[index create CountiesIndex CitiesIndex])
end

it 'allows to pass --settings as a hash with imploded values' do
expect(CountiesIndex).to receive(:create_index).with(alias: false, settings: { 'index.refresh_interval': '1s' }).and_return(true)
cli_exec(%w[index create CountiesIndex --settings=index.refresh_interval:1s])
end
end
end
end
5 changes: 5 additions & 0 deletions spec/esse/cli/index/reset_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,11 @@
expect(CountiesIndex).to receive(:reset_index).with(import: true, optimize: false).and_return(true)
cli_exec(%w[index reset CountiesIndex --no-optimize])
end

it 'allows to pass --settings as a hash with imploded values' do
expect(CountiesIndex).to receive(:reset_index).with(import: true, optimize: true, settings: { 'index.refresh_interval': '-1' }).and_return(true)
cli_exec(%w[index reset CountiesIndex --settings=index.refresh_interval:-1])
end
end
end
end
5 changes: 5 additions & 0 deletions spec/esse/cli/index/update_settings_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,11 @@
expect(CitiesIndex).to receive(:update_settings).and_return(true)
cli_exec(%w[index update_settings CountiesIndex CitiesIndex])
end

it 'allows to pass --settings as a hash with imploded values' do
expect(CountiesIndex).to receive(:update_settings).with(settings: { 'index.refresh_interval': '1s' }).and_return(true)
cli_exec(%w[index update_settings CountiesIndex --settings=index.refresh_interval:1s])
end
end
end
end
Loading

0 comments on commit e370f09

Please sign in to comment.