connectors_service 8.7.0.0.pre.20221117T010623Z → 8.11.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (55) hide show
  1. checksums.yaml +4 -4
  2. data/config/connectors.yml +10 -8
  3. data/lib/app/config.rb +6 -1
  4. data/lib/app/console_app.rb +1 -1
  5. data/lib/app/dispatcher.rb +18 -3
  6. data/lib/connectors/base/connector.rb +39 -22
  7. data/lib/connectors/crawler/scheduler.rb +36 -0
  8. data/lib/connectors/example/connector.rb +2 -2
  9. data/lib/connectors/example/example_advanced_snippet_validator.rb +4 -3
  10. data/lib/connectors/gitlab/connector.rb +4 -4
  11. data/lib/connectors/gitlab/gitlab_advanced_snippet_validator.rb +8 -10
  12. data/lib/{connectors_app/// → connectors/job_trigger_method.rb} +6 -5
  13. data/lib/connectors/mongodb/connector.rb +66 -56
  14. data/lib/connectors/mongodb/mongo_advanced_snippet_against_schema_validator.rb +2 -2
  15. data/lib/connectors/mongodb/mongo_advanced_snippet_schema.rb +3 -2
  16. data/lib/connectors/mongodb/mongo_advanced_snippet_snake_case_transformer.rb +49 -0
  17. data/lib/connectors/registry.rb +1 -1
  18. data/lib/connectors/tolerable_error_helper.rb +5 -1
  19. data/lib/connectors_utility.rb +6 -3
  20. data/lib/core/configuration.rb +13 -1
  21. data/lib/core/connector_job.rb +48 -7
  22. data/lib/core/connector_settings.rb +52 -20
  23. data/lib/core/elastic_connector_actions.rb +54 -38
  24. data/lib/core/filtering/advanced_snippet/advanced_snippet_against_schema_validator.rb +32 -0
  25. data/lib/core/filtering/advanced_snippet/advanced_snippet_validator.rb +27 -0
  26. data/lib/core/filtering/filter_validator.rb +103 -0
  27. data/lib/{connectors/base/advanced_snippet_against_schema_validator.rb → core/filtering/hash_against_schema_validator.rb} +58 -44
  28. data/lib/core/filtering/post_process_engine.rb +2 -2
  29. data/lib/core/filtering/processing_stage.rb +20 -0
  30. data/lib/core/filtering/{simple_rule.rb → simple_rules/simple_rule.rb} +34 -1
  31. data/lib/core/filtering/simple_rules/simple_rules_parser.rb +44 -0
  32. data/lib/core/filtering/simple_rules/validation/no_conflicting_policies_rules_validator.rb +47 -0
  33. data/lib/core/filtering/simple_rules/validation/simple_rules_schema.rb +68 -0
  34. data/lib/core/filtering/simple_rules/validation/simple_rules_validator.rb +25 -0
  35. data/lib/core/filtering/simple_rules/validation/single_rule_against_schema_validator.rb +37 -0
  36. data/lib/core/filtering/transform/filter_transformer.rb +26 -0
  37. data/lib/core/filtering/transform/filter_transformer_facade.rb +61 -0
  38. data/lib/core/filtering/transform/transformation_target.rb +10 -0
  39. data/lib/core/filtering/validation_job_runner.rb +1 -3
  40. data/lib/core/filtering.rb +5 -3
  41. data/lib/core/job_cleanup.rb +66 -0
  42. data/lib/core/jobs/consumer.rb +62 -64
  43. data/lib/core/jobs/producer.rb +3 -0
  44. data/lib/core/scheduler.rb +67 -52
  45. data/lib/core/sync_job_runner.rb +170 -83
  46. data/lib/core.rb +1 -0
  47. data/lib/utility/bulk_queue.rb +1 -1
  48. data/lib/utility/constants.rb +0 -2
  49. data/lib/utility/error_monitor.rb +26 -5
  50. data/lib/utility/es_client.rb +4 -0
  51. data/lib/utility/filtering.rb +4 -0
  52. metadata +32 -21
  53. data/lib/connectors/base/advanced_snippet_validator.rb +0 -34
  54. data/lib/connectors/base/simple_rules_parser.rb +0 -42
  55. data/lib/connectors/mongodb/mongo_rules_parser.rb +0 -81
@@ -0,0 +1,49 @@
1
+ #
2
+ # Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3
+ # or more contributor license agreements. Licensed under the Elastic License;
4
+ # you may not use this file except in compliance with the Elastic License.
5
+ #
6
+
7
+ # frozen_string_literal: true
8
+
9
+ require 'core/filtering/transform/filter_transformer'
10
+ require 'active_support'
11
+
12
+ module Connectors
13
+ module MongoDB
14
+ class MongoAdvancedSnippetSnakeCaseTransformer < Core::Filtering::Transform::FilterTransformer
15
+
16
+ def initialize(advanced_snippet = {})
17
+ super
18
+
19
+ @advanced_snippet = advanced_snippet
20
+ @transformation = ->(snippet) { snake_case_filter(snippet) }
21
+ end
22
+
23
+ private
24
+
25
+ def snake_case_filter(advanced_snippet, transformed_filter = {})
26
+ advanced_snippet&.each do |key, value|
27
+ snake_case_key = key.to_s.underscore
28
+
29
+ value = value.is_a?(Hash) ? snake_case_filter(value, {}) : value
30
+
31
+ if value.is_a?(Array)
32
+ new_entries = []
33
+
34
+ value.each do |entry|
35
+ new_entry = entry.is_a?(Hash) ? snake_case_filter(entry, {}) : entry
36
+ new_entries.push(new_entry)
37
+ end
38
+
39
+ value = new_entries
40
+ end
41
+
42
+ transformed_filter[snake_case_key] = value
43
+ end
44
+
45
+ transformed_filter
46
+ end
47
+ end
48
+ end
49
+ end
@@ -24,7 +24,7 @@ module Connectors
24
24
  @connectors[name]
25
25
  end
26
26
 
27
- def connector(name, configuration, job_description: {})
27
+ def connector(name, configuration, job_description: nil)
28
28
  klass = connector_class(name)
29
29
  if klass.present?
30
30
  return klass.new(configuration: configuration, job_description: job_description)
@@ -36,7 +36,11 @@ module Connectors
36
36
 
37
37
  def fatal_exception_classes
38
38
  [
39
- Utility::ErrorMonitor::MonitoringError
39
+ Utility::ErrorMonitor::MonitoringError,
40
+ Core::ConnectorNotFoundError,
41
+ Core::ConnectorJobNotFoundError,
42
+ Core::ConnectorJobCanceledError,
43
+ Core::ConnectorJobNotRunningError
40
44
  ]
41
45
  end
42
46
  end
@@ -9,8 +9,11 @@
9
9
  require_relative 'utility'
10
10
 
11
11
  require_relative 'connectors/connector_status'
12
+ require_relative 'connectors/crawler/scheduler'
13
+ require_relative 'connectors/job_trigger_method'
12
14
  require_relative 'connectors/sync_status'
13
- require_relative 'core/scheduler'
15
+ require_relative 'core/connector_job'
16
+ require_relative 'core/connector_settings'
14
17
  require_relative 'core/elastic_connector_actions'
15
-
16
- require_relative 'connectors/crawler/scheduler'
18
+ require_relative 'core/filtering/validation_status'
19
+ require_relative 'core/scheduler'
@@ -24,7 +24,19 @@ module Core
24
24
  return
25
25
  end
26
26
  configuration = connector_class.configurable_fields_indifferent_access
27
- features = connector_class.kibana_features.each_with_object({}) { |feature, hsh| hsh[feature] = true }
27
+
28
+ features = {}
29
+
30
+ connector_class.kibana_features.each do |feature_definition, _hsh|
31
+ feature = feature_definition[:feature]
32
+ subfeature = feature_definition[:subfeature]
33
+ enabled = feature_definition[:enabled]
34
+
35
+ features[feature] = {} unless features.key?(feature)
36
+
37
+ features[feature][subfeature] = { :enabled => enabled }
38
+ end
39
+
28
40
  doc = {
29
41
  :configuration => configuration,
30
42
  :features => features
@@ -8,12 +8,14 @@
8
8
 
9
9
  require 'active_support/core_ext/hash/indifferent_access'
10
10
  require 'connectors/sync_status'
11
+ require 'core/connector_settings'
11
12
  require 'core/elastic_connector_actions'
12
13
  require 'utility'
13
14
 
14
15
  module Core
15
16
  class ConnectorJob
16
17
  DEFAULT_PAGE_SIZE = 100
18
+ IDLE_THRESHOLD = 60
17
19
 
18
20
  def self.fetch_by_id(job_id)
19
21
  es_response = ElasticConnectorActions.get_job(job_id)
@@ -34,12 +36,32 @@ module Core
34
36
  fetch_jobs_by_query(query, page_size)
35
37
  end
36
38
 
37
- def self.orphaned_jobs(_page_size = DEFAULT_PAGE_SIZE)
38
- []
39
+ def self.orphaned_jobs(connector_ids = [], page_size = DEFAULT_PAGE_SIZE)
40
+ query = { bool: { must_not: { terms: { 'connector.id': connector_ids } } } }
41
+ fetch_jobs_by_query(query, page_size)
39
42
  end
40
43
 
41
- def self.stuck_jobs(_page_size = DEFAULT_PAGE_SIZE)
42
- []
44
+ def self.delete_jobs(jobs)
45
+ query = { terms: { '_id': jobs.map(&:id) } }
46
+ ElasticConnectorActions.delete_jobs_by_query(query)
47
+ end
48
+
49
+ def self.idle_jobs(connector_id = nil, page_size = DEFAULT_PAGE_SIZE)
50
+ connector_ids = if connector_id
51
+ [connector_id]
52
+ else
53
+ ConnectorSettings.fetch_native_connectors.map(&:id)
54
+ end
55
+ query = {
56
+ bool: {
57
+ filter: [
58
+ { terms: { 'connector.id': connector_ids } },
59
+ { terms: { status: Connectors::SyncStatus::ACTIVE_STATUSES } },
60
+ { range: { last_seen: { lte: "now-#{IDLE_THRESHOLD}s" } } }
61
+ ]
62
+ }
63
+ }
64
+ fetch_jobs_by_query(query, page_size)
43
65
  end
44
66
 
45
67
  def self.enqueue(_connector_id)
@@ -95,7 +117,7 @@ module Core
95
117
  end
96
118
 
97
119
  def connector_id
98
- @elasticsearch_response[:_source][:connector][:id]
120
+ connector_snapshot[:id]
99
121
  end
100
122
 
101
123
  def index_name
@@ -115,17 +137,36 @@ module Core
115
137
  end
116
138
 
117
139
  def filtering
118
- Utility::Filtering.extract_filter(connector_snapshot[:filtering])
140
+ connector_snapshot[:filtering]
119
141
  end
120
142
 
121
143
  def pipeline
122
- @elasticsearch_response[:_source][:pipeline]
144
+ connector_snapshot[:pipeline] || {}
145
+ end
146
+
147
+ def extract_binary_content?
148
+ pipeline[:extract_binary_content]
149
+ end
150
+
151
+ def reduce_whitespace?
152
+ pipeline[:reduce_whitespace]
153
+ end
154
+
155
+ def run_ml_inference?
156
+ pipeline[:run_ml_inference]
123
157
  end
124
158
 
125
159
  def connector
126
160
  @connector ||= ConnectorSettings.fetch_by_id(connector_id)
127
161
  end
128
162
 
163
+ def update_metadata(ingestion_stats = {}, connector_metadata = {})
164
+ ingestion_stats ||= {}
165
+ doc = { :last_seen => Time.now }.merge(ingestion_stats)
166
+ doc[:metadata] = connector_metadata if connector_metadata&.any?
167
+ ElasticConnectorActions.update_job_fields(id, doc)
168
+ end
169
+
129
170
  def done!(ingestion_stats = {}, connector_metadata = {})
130
171
  terminate!(Connectors::SyncStatus::COMPLETED, nil, ingestion_stats, connector_metadata)
131
172
  end
@@ -8,6 +8,7 @@
8
8
 
9
9
  require 'active_support/core_ext/hash/indifferent_access'
10
10
  require 'connectors/connector_status'
11
+ require 'connectors/sync_status'
11
12
  require 'core/elastic_connector_actions'
12
13
  require 'utility'
13
14
 
@@ -49,6 +50,11 @@ module Core
49
50
  fetch_connectors_by_query(query, page_size)
50
51
  end
51
52
 
53
+ def self.fetch_all_connectors(page_size = DEFAULT_PAGE_SIZE)
54
+ query = { match_all: {} }
55
+ fetch_connectors_by_query(query, page_size)
56
+ end
57
+
52
58
  def id
53
59
  @elasticsearch_response[:_id]
54
60
  end
@@ -58,6 +64,24 @@ module Core
58
64
  @elasticsearch_response[:_source][property_name]
59
65
  end
60
66
 
67
+ def features
68
+ self[:features] || {}
69
+ end
70
+
71
+ # .dig version is the modern features way of doing things,
72
+ # Right-hand of OR operator is legacy features support
73
+ # When this is fixed with a migration, we can go ahead
74
+ def filtering_rule_feature_enabled?
75
+ !!features.dig(:sync_rules, :basic, :enabled) || !!features[:filtering_rules]
76
+ end
77
+ def filtering_advanced_config_feature_enabled?
78
+ !!features.dig(:sync_rules, :advanced, :enabled) || !!features[:filtering_advanced_config]
79
+ end
80
+
81
+ def any_filtering_feature_enabled?
82
+ filtering_rule_feature_enabled? || filtering_advanced_config_feature_enabled?
83
+ end
84
+
61
85
  def index_name
62
86
  self[:index_name]
63
87
  end
@@ -79,30 +103,34 @@ module Core
79
103
  end
80
104
 
81
105
  def scheduling_settings
82
- self[:scheduling]
106
+ self[:scheduling] || {}
83
107
  end
84
108
 
85
- def filtering
86
- # assume for now, that first object in filtering array or a filter object itself is the only filtering object
87
- filtering = @elasticsearch_response.dig(:_source, :filtering)
109
+ def full_sync_scheduling
110
+ scheduling_settings[:full]
111
+ end
88
112
 
89
- Utility::Filtering.extract_filter(filtering)
113
+ def custom_scheduling_settings
114
+ self[:custom_scheduling]
90
115
  end
91
116
 
92
- def request_pipeline
93
- Utility::Common.return_if_present(@elasticsearch_response.dig(:_source, :pipeline, :name), @connectors_meta.dig(:pipeline, :default_name), DEFAULT_REQUEST_PIPELINE)
117
+ def sync_now?
118
+ self[:sync_now] == true
94
119
  end
95
120
 
96
- def extract_binary_content?
97
- Utility::Common.return_if_present(@elasticsearch_response.dig(:_source, :pipeline, :extract_binary_content), @connectors_meta.dig(:pipeline, :default_extract_binary_content), DEFAULT_EXTRACT_BINARY_CONTENT)
121
+ def last_synced
122
+ self[:last_synced]
98
123
  end
99
124
 
100
- def reduce_whitespace?
101
- Utility::Common.return_if_present(@elasticsearch_response.dig(:_source, :pipeline, :reduce_whitespace), @connectors_meta.dig(:pipeline, :default_reduce_whitespace), DEFAULT_REDUCE_WHITESPACE)
125
+ def filtering
126
+ # assume for now, that first object in filtering array or a filter object itself is the only filtering object
127
+ filtering = @elasticsearch_response.dig(:_source, :filtering)
128
+
129
+ Utility::Filtering.extract_filter(filtering)
102
130
  end
103
131
 
104
- def run_ml_inference?
105
- Utility::Common.return_if_present(@elasticsearch_response.dig(:_source, :pipeline, :run_ml_inference), @connectors_meta.dig(:pipeline, :default_run_ml_inference), DEFAULT_RUN_ML_INFERENCE)
132
+ def request_pipeline
133
+ Utility::Common.return_if_present(@elasticsearch_response.dig(:_source, :pipeline, :name), @connectors_meta.dig(:pipeline, :default_name), DEFAULT_REQUEST_PIPELINE)
106
134
  end
107
135
 
108
136
  def formatted
@@ -130,19 +158,23 @@ module Core
130
158
  end
131
159
 
132
160
  def update_last_sync!(job)
161
+ # if job is nil, connector still needs to be updated, to avoid it stuck at in_progress
162
+ job_status = job&.status || Connectors::SyncStatus::ERROR
163
+ job_error = job.nil? ? 'Could\'t find the job' : job.error
164
+ job_error ||= 'unknown error' if job_status == Connectors::SyncStatus::ERROR
165
+ connector_status = (job_status == Connectors::SyncStatus::ERROR ? Connectors::ConnectorStatus::ERROR : Connectors::ConnectorStatus::CONNECTED)
133
166
  doc = {
134
- :last_sync_status => job.status,
167
+ :last_sync_status => job_status,
135
168
  :last_synced => Time.now,
136
- :last_sync_error => job.error,
137
- :error => job.error
169
+ :last_sync_error => job_error,
170
+ :status => connector_status,
171
+ :error => job_error
138
172
  }
139
-
140
- if job.terminated?
173
+ if job&.terminated?
141
174
  doc[:last_indexed_document_count] = job[:indexed_document_count]
142
175
  doc[:last_deleted_document_count] = job[:deleted_document_count]
143
176
  end
144
-
145
- Core::ElasticConnectorActions.update_connector_fields(job.connector_id, doc)
177
+ Core::ElasticConnectorActions.update_connector_fields(id, doc)
146
178
  end
147
179
 
148
180
  private
@@ -8,6 +8,7 @@
8
8
  #
9
9
  require 'active_support/core_ext/hash'
10
10
  require 'connectors/connector_status'
11
+ require 'connectors/job_trigger_method'
11
12
  require 'connectors/sync_status'
12
13
  require 'utility'
13
14
  require 'elastic-transport'
@@ -60,9 +61,14 @@ module Core
60
61
 
61
62
  def connectors_meta
62
63
  # TODO: remove the usage of with_indifferent_access. Ideally this should return a hash or nil if not found
63
- alias_mappings = client.indices.get_mapping(:index => Utility::Constants::CONNECTORS_INDEX).with_indifferent_access
64
+ alias_mappings = client.indices.get_mapping(:index => Utility::Constants::CONNECTORS_INDEX, :ignore => 404).with_indifferent_access
64
65
  index = get_latest_index_in_alias(Utility::Constants::CONNECTORS_INDEX, alias_mappings.keys)
65
- alias_mappings.dig(index, 'mappings', '_meta') || {}
66
+ alias_mappings.dig(index, 'mappings', '_meta') || {
67
+ :extract_binary_content => true,
68
+ :name => 'ent-search-generic-ingestion',
69
+ :reduce_whitespace => true,
70
+ :run_ml_inference => false,
71
+ }
66
72
  end
67
73
 
68
74
  def search_connectors(query, page_size, offset)
@@ -91,6 +97,17 @@ module Core
91
97
  )
92
98
  end
93
99
 
100
+ def delete_jobs_by_query(query)
101
+ client.delete_by_query(
102
+ :index => Utility::Constants::JOB_INDEX,
103
+ :body => { :query => query }
104
+ )
105
+ end
106
+
107
+ def delete_indices(indices)
108
+ client.indices.delete(:index => indices, :ignore_unavailable => true)
109
+ end
110
+
94
111
  def update_connector_configuration(connector_id, configuration)
95
112
  update_connector_fields(connector_id, :configuration => configuration)
96
113
  end
@@ -145,12 +162,37 @@ module Core
145
162
  )
146
163
  end
147
164
 
148
- def update_connector_last_sync_status(connector_id, last_sync_status)
165
+ def update_connector_sync_start(connector_id)
149
166
  doc = connector_with_concurrency_control(connector_id)
150
167
 
168
+ body = {
169
+ last_sync_status: Connectors::SyncStatus::IN_PROGRESS,
170
+ last_sync_error: nil,
171
+ status: Connectors::ConnectorStatus::CONNECTED
172
+ }
173
+
151
174
  update_connector_fields(
152
175
  connector_id,
153
- { last_sync_status: last_sync_status },
176
+ body,
177
+ doc[:seq_no],
178
+ doc[:primary_term]
179
+ )
180
+ end
181
+
182
+ def update_connector_custom_scheduling_last_synced(connector_id, schedule_key)
183
+ doc = connector_with_concurrency_control(connector_id)
184
+
185
+ body = {
186
+ :custom_scheduling => {
187
+ schedule_key => {
188
+ :last_synced => Time.now
189
+ }
190
+ }
191
+ }
192
+
193
+ update_connector_fields(
194
+ connector_id,
195
+ body,
154
196
  doc[:seq_no],
155
197
  doc[:primary_term]
156
198
  )
@@ -178,13 +220,15 @@ module Core
178
220
  status: Connectors::SyncStatus::PENDING,
179
221
  created_at: Time.now,
180
222
  last_seen: Time.now,
223
+ trigger_method: connector_settings.sync_now? ? Connectors::JobTriggerMethod::ON_DEMAND : Connectors::JobTriggerMethod::SCHEDULED,
181
224
  connector: {
182
225
  id: connector_settings.id,
183
226
  filtering: convert_connector_filtering_to_job_filtering(connector_settings.filtering),
184
227
  index_name: connector_settings.index_name,
185
228
  language: connector_settings[:language],
186
229
  pipeline: connector_settings[:pipeline],
187
- service_type: connector_settings.service_type
230
+ service_type: connector_settings.service_type,
231
+ configuration: connector_settings.configuration
188
232
  }
189
233
  }
190
234
 
@@ -220,37 +264,6 @@ module Core
220
264
  update_connector_fields(connector_id, body)
221
265
  end
222
266
 
223
- def update_sync(job_id, metadata)
224
- body = {
225
- :doc => { :last_seen => Time.now }.merge(metadata)
226
- }
227
- client.update(:index => Utility::Constants::JOB_INDEX, :id => job_id, :body => body)
228
- end
229
-
230
- def complete_sync(connector_id, job_id, metadata, error)
231
- sync_status = error ? Connectors::SyncStatus::ERROR : Connectors::SyncStatus::COMPLETED
232
-
233
- metadata ||= {}
234
-
235
- update_connector_fields(connector_id,
236
- :last_sync_status => sync_status,
237
- :last_sync_error => error,
238
- :error => error,
239
- :last_synced => Time.now,
240
- :last_indexed_document_count => metadata[:indexed_document_count],
241
- :last_deleted_document_count => metadata[:deleted_document_count])
242
-
243
- body = {
244
- :doc => {
245
- :status => sync_status,
246
- :completed_at => Time.now,
247
- :last_seen => Time.now,
248
- :error => error
249
- }.merge(metadata)
250
- }
251
- client.update(:index => Utility::Constants::JOB_INDEX, :id => job_id, :body => body)
252
- end
253
-
254
267
  def fetch_document_ids(index_name)
255
268
  page_size = 1000
256
269
  result = []
@@ -331,9 +344,11 @@ module Core
331
344
  # Creation of connector index should be handled by Kibana, this method is only used by ftest.rb
332
345
  def ensure_connectors_index_exists
333
346
  mappings = {
347
+ :dynamic => false,
334
348
  :properties => {
335
349
  :api_key_id => { :type => :keyword },
336
350
  :configuration => { :type => :object },
351
+ :custom_schedule => { :type => :object },
337
352
  :description => { :type => :text },
338
353
  :error => { :type => :keyword },
339
354
  :features => {
@@ -451,6 +466,7 @@ module Core
451
466
  # Creation of job index should be handled by Kibana, this method is only used by ftest.rb
452
467
  def ensure_job_index_exists
453
468
  mappings = {
469
+ :dynamic => false,
454
470
  :properties => {
455
471
  :cancelation_requested_at => { :type => :date },
456
472
  :canceled_at => { :type => :date },
@@ -528,8 +544,8 @@ module Core
528
544
  end
529
545
 
530
546
  def document_count(index_name)
531
- client.indices.refresh(:index => index_name)
532
- client.count(:index => index_name)['count']
547
+ client.indices.refresh(:index => index_name, :ignore_unavailable => true)
548
+ client.count(:index => index_name, :ignore_unavailable => true)['count']
533
549
  end
534
550
 
535
551
  private
@@ -0,0 +1,32 @@
1
+ #
2
+ # Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3
+ # or more contributor license agreements. Licensed under the Elastic License;
4
+ # you may not use this file except in compliance with the Elastic License.
5
+ #
6
+ # frozen_string_literal: true
7
+
8
+ require 'active_support/core_ext/hash'
9
+ require 'utility/logger'
10
+ require 'core/filtering/advanced_snippet/advanced_snippet_validator'
11
+ require 'core/filtering/validation_status'
12
+ require 'core/filtering/hash_against_schema_validator'
13
+
14
+ module Core
15
+ module Filtering
16
+ module AdvancedSnippet
17
+ class AdvancedSnippetAgainstSchemaValidator < Core::Filtering::AdvancedSnippet::AdvancedSnippetValidator
18
+
19
+ def initialize(advanced_snippet, schema)
20
+ super(advanced_snippet)
21
+ @schema = schema
22
+ @schema_validator = Core::Filtering::SchemaValidator.new(schema: schema, payload: advanced_snippet, error_id: ADVANCED_SNIPPET_ID)
23
+ end
24
+
25
+ def is_snippet_valid
26
+ @schema_validator.validate_against_schema
27
+ end
28
+
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,27 @@
1
+ #
2
+ # Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3
+ # or more contributor license agreements. Licensed under the Elastic License;
4
+ # you may not use this file except in compliance with the Elastic License.
5
+ #
6
+ # frozen_string_literal: true
7
+
8
+ require 'utility/logger'
9
+
10
+ module Core
11
+ module Filtering
12
+ module AdvancedSnippet
13
+ class AdvancedSnippetValidator
14
+
15
+ ADVANCED_SNIPPET_ID = 'advanced_snippet'
16
+
17
+ def initialize(advanced_snippet)
18
+ @advanced_snippet = advanced_snippet || {}
19
+ end
20
+
21
+ def is_snippet_valid
22
+ raise 'Advanced Snippet validation not implemented'
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,103 @@
1
+ #
2
+ # Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
3
+ # or more contributor license agreements. Licensed under the Elastic License;
4
+ # you may not use this file except in compliance with the Elastic License.
5
+ #
6
+ # frozen_string_literal: true
7
+
8
+ require 'core/filtering/validation_status'
9
+ require 'core/filtering/processing_stage'
10
+ require 'utility/logger'
11
+
12
+ module Core
13
+ module Filtering
14
+ class FilterValidator
15
+
16
+ ADVANCED_SNIPPET = 'Advanced Snippet'
17
+ SIMPLE_RULES = 'Simple Rules'
18
+
19
+ def initialize(snippet_validator_classes: [], rules_validator_classes: [], rules_pre_processing_active: false)
20
+ @snippet_validators_classes = {
21
+ 'classes' => extract_advanced_snippet_validators(snippet_validator_classes),
22
+ 'type' => ADVANCED_SNIPPET
23
+ }
24
+
25
+ @rules_validator_classes = {
26
+ 'classes' => extract_simple_rule_validators(rules_validator_classes, rules_pre_processing_active),
27
+ 'type' => SIMPLE_RULES
28
+ }
29
+ end
30
+
31
+ def is_filter_valid(filter = {})
32
+ return { :state => Core::Filtering::ValidationStatus::VALID, :errors => [] } unless filter.present?
33
+
34
+ snippet_validation_result = execute_validation(@snippet_validators_classes, filter)
35
+ log_validation_result(snippet_validation_result, ADVANCED_SNIPPET)
36
+
37
+ rules_validation_result = execute_validation(@rules_validator_classes, filter)
38
+ log_validation_result(rules_validation_result, SIMPLE_RULES)
39
+
40
+ merge_validation_results(snippet_validation_result, rules_validation_result)
41
+ end
42
+
43
+ private
44
+
45
+ def log_validation_result(validation_result, validator_type)
46
+ Utility::Logger.info("Filtering #{validator_type} validation result: #{validation_result[:state]}")
47
+ if validation_result[:errors].present?
48
+ validation_result[:errors].each do |error|
49
+ Utility::Logger.warn("Filtering #{validator_type} validation error for: '#{error[:ids]}': '#{error[:messages]}'")
50
+ end
51
+ end
52
+ end
53
+
54
+ def merge_validation_results(*validation_results)
55
+ merged_result = { :state => Core::Filtering::ValidationStatus::VALID, :errors => [] }
56
+
57
+ validation_results.each do |validation_result|
58
+ merged_result[:state] = Core::Filtering::ValidationStatus::INVALID if validation_result[:state] == Core::Filtering::ValidationStatus::INVALID
59
+ merged_result[:errors].push(*validation_result[:errors]) if validation_result[:errors].present?
60
+ end
61
+
62
+ merged_result
63
+ end
64
+
65
+ def execute_validation(validators, filter)
66
+ validation_results = []
67
+
68
+ validator_type = validators['type']
69
+ advanced_snippet = filter.dig('advanced_snippet', 'value')
70
+
71
+ validators['classes'].each do |validator_class|
72
+ case validator_type
73
+ when ADVANCED_SNIPPET
74
+ validation_result = validator_class.new(advanced_snippet).is_snippet_valid
75
+ when SIMPLE_RULES
76
+ validation_result = validator_class.new(filter['rules']).are_rules_valid
77
+ else
78
+ raise "Unknown validator: #{validator_type}"
79
+ end
80
+
81
+ validation_results.push(validation_result) if validation_result[:state] == Core::Filtering::ValidationStatus::INVALID
82
+ end
83
+
84
+ merge_validation_results(*validation_results)
85
+ end
86
+
87
+ def extract_advanced_snippet_validators(snippet_validators)
88
+ snippet_validators.is_a?(Array) ? snippet_validators : [snippet_validators]
89
+ end
90
+
91
+ def extract_simple_rule_validators(rule_validators, pre_processing_active)
92
+ return rule_validators if rule_validators.is_a?(Array)
93
+
94
+ common_validators = rule_validators[Core::Filtering::ProcessingStage::ALL] || []
95
+ pre_validators = rule_validators[Core::Filtering::ProcessingStage::PRE] || []
96
+ post_validators = rule_validators[Core::Filtering::ProcessingStage::POST] || []
97
+
98
+ # post processing validation will always be executed
99
+ pre_processing_active ? common_validators + pre_validators + post_validators : common_validators + post_validators
100
+ end
101
+ end
102
+ end
103
+ end