connectors_service 8.6.0.4.pre.20221104T200814Z → 8.6.0.4.pre.20221114T233727Z
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/config/connectors.yml +6 -6
- data/lib/app/dispatcher.rb +12 -0
- data/lib/app/preflight_check.rb +11 -0
- data/lib/connectors/base/connector.rb +19 -12
- data/lib/connectors/base/simple_rules_parser.rb +42 -0
- data/lib/connectors/example/connector.rb +15 -0
- data/lib/connectors/gitlab/connector.rb +15 -1
- data/lib/connectors/mongodb/connector.rb +55 -36
- data/lib/connectors/mongodb/mongo_rules_parser.rb +81 -0
- data/lib/core/configuration.rb +3 -1
- data/lib/core/connector_job.rb +137 -0
- data/lib/core/connector_settings.rb +24 -11
- data/lib/core/elastic_connector_actions.rb +263 -24
- data/lib/core/filtering/post_process_engine.rb +39 -0
- data/lib/core/filtering/post_process_result.rb +27 -0
- data/lib/core/filtering/simple_rule.rb +141 -0
- data/lib/core/filtering/validation_job_runner.rb +53 -0
- data/lib/core/filtering/validation_status.rb +17 -0
- data/lib/core/filtering.rb +17 -0
- data/lib/core/ingestion/es_sink.rb +59 -0
- data/lib/core/ingestion/ingester.rb +90 -0
- data/lib/core/{output_sink.rb → ingestion.rb} +2 -5
- data/lib/core/scheduler.rb +40 -10
- data/lib/core/sync_job_runner.rb +65 -17
- data/lib/core.rb +2 -0
- data/lib/utility/bulk_queue.rb +85 -0
- data/lib/utility/constants.rb +2 -0
- data/lib/utility/filtering.rb +22 -0
- data/lib/utility/logger.rb +2 -1
- data/lib/utility.rb +5 -4
- metadata +16 -7
- data/lib/core/output_sink/base_sink.rb +0 -33
- data/lib/core/output_sink/combined_sink.rb +0 -38
- data/lib/core/output_sink/console_sink.rb +0 -51
- data/lib/core/output_sink/es_sink.rb +0 -74
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8f69f05260d34b07ce34d569ce7c41fdd10349b33121823697ebbb6a4ebf9206
|
4
|
+
data.tar.gz: a2957118c80d0e2bc9ea6a8046307485c11e4f809efb01cabbb96e341dc947c2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 63775eded9d9953b41950edd7ca86176200c4ae7510564f6f7995c336d6e78bbe40d494cb0a152a984b14b45055e7b7847a2779888d70905d4956b8c78d4bda1
|
7
|
+
data.tar.gz: 52b00d122ef43fc5afa0b4cb50bbe428111e7fe2cb7cee437dd2d2b6b32516cbd01c5803b0a11609a5021e85605a2e6bd2b30973807df3cae1420864e2fcb185
|
data/config/connectors.yml
CHANGED
@@ -1,10 +1,10 @@
|
|
1
1
|
# general metadata
|
2
|
-
version: 8.6.0.4-
|
2
|
+
version: 8.6.0.4-20221114T233727Z
|
3
3
|
repository: git@github.com:elastic/ent-search-connectors.git
|
4
|
-
revision:
|
4
|
+
revision: f506d5e5ebedfb0c6058d347d8ce22adc42e2cc0
|
5
5
|
elasticsearch:
|
6
6
|
hosts: http://localhost:9200
|
7
|
-
api_key:
|
7
|
+
api_key: WXNYeWQ0UUJ4Y3ZQV3ctbjVibnU6REx4eE8tbFhUMU94N2JoU2hIeVFMQQ==
|
8
8
|
retry_on_failure: 3
|
9
9
|
request_timeout: 120
|
10
10
|
disable_warnings: true
|
@@ -14,11 +14,11 @@ thread_pool:
|
|
14
14
|
min_threads: 0
|
15
15
|
max_threads: 5
|
16
16
|
max_queue: 100
|
17
|
-
log_level:
|
17
|
+
log_level: debug
|
18
18
|
ecs_logging: false
|
19
19
|
poll_interval: 3
|
20
20
|
termination_timeout: 60
|
21
21
|
heartbeat_interval: 1800
|
22
22
|
native_mode: false
|
23
|
-
connector_id:
|
24
|
-
service_type:
|
23
|
+
connector_id: YcXyd4QBxcvPWw-n2bkA
|
24
|
+
service_type: mongodb
|
data/lib/app/dispatcher.rb
CHANGED
@@ -73,6 +73,8 @@ module App
|
|
73
73
|
start_heartbeat_task(connector_settings)
|
74
74
|
when :configuration
|
75
75
|
start_configuration_task(connector_settings)
|
76
|
+
when :filter_validation
|
77
|
+
start_filter_validation_task(connector_settings)
|
76
78
|
else
|
77
79
|
Utility::Logger.error("Unknown task type: #{task}. Skipping...")
|
78
80
|
end
|
@@ -120,6 +122,16 @@ module App
|
|
120
122
|
Utility::ExceptionTracking.log_exception(e, "Configuration task for #{connector_settings.formatted} failed due to unexpected error.")
|
121
123
|
end
|
122
124
|
end
|
125
|
+
|
126
|
+
def start_filter_validation_task(connector_settings)
|
127
|
+
pool.post do
|
128
|
+
Utility::Logger.info("Validating filters for #{connector_settings.formatted}...")
|
129
|
+
validation_job_runner = Core::Filtering::ValidationJobRunner.new(connector_settings)
|
130
|
+
validation_job_runner.execute
|
131
|
+
rescue StandardError => e
|
132
|
+
Utility::ExceptionTracking.log_exception(e, "Filter validation task for #{connector_settings.formatted} failed due to unexpected error.")
|
133
|
+
end
|
134
|
+
end
|
123
135
|
end
|
124
136
|
end
|
125
137
|
end
|
data/lib/app/preflight_check.rb
CHANGED
@@ -23,6 +23,7 @@ module App
|
|
23
23
|
check_es_connection!
|
24
24
|
check_es_version!
|
25
25
|
check_system_indices!
|
26
|
+
check_single_connector!
|
26
27
|
end
|
27
28
|
|
28
29
|
private
|
@@ -59,6 +60,16 @@ module App
|
|
59
60
|
)
|
60
61
|
end
|
61
62
|
|
63
|
+
#-------------------------------------------------------------------------------------------------
|
64
|
+
# Ensures the connector is supported when running in non-native mode
|
65
|
+
def check_single_connector!
|
66
|
+
if App::Config.native_mode
|
67
|
+
Utility::Logger.info('Skip single connector check for native mode.')
|
68
|
+
elsif !Connectors::REGISTRY.registered?(App::Config.service_type)
|
69
|
+
fail_check!("The service type #{App::Config.service_type} is not supported. Terminating...")
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
62
73
|
def check_es_connection_with_retries!(retry_interval:, retry_timeout:)
|
63
74
|
started_at = Time.now
|
64
75
|
|
@@ -7,8 +7,9 @@
|
|
7
7
|
# frozen_string_literal: true
|
8
8
|
|
9
9
|
require 'bson'
|
10
|
-
require 'core/
|
10
|
+
require 'core/ingestion'
|
11
11
|
require 'utility'
|
12
|
+
require 'utility/filtering'
|
12
13
|
require 'app/config'
|
13
14
|
require 'active_support/core_ext/hash/indifferent_access'
|
14
15
|
|
@@ -32,16 +33,27 @@ module Connectors
|
|
32
33
|
raise 'Not implemented for this connector'
|
33
34
|
end
|
34
35
|
|
36
|
+
def self.kibana_features
|
37
|
+
[
|
38
|
+
Utility::Constants::FILTERING_RULES_FEATURE,
|
39
|
+
Utility::Constants::FILTERING_ADVANCED_FEATURE
|
40
|
+
]
|
41
|
+
end
|
42
|
+
|
43
|
+
def self.validate_filtering(_filtering = {})
|
44
|
+
raise 'Not implemented for this connector'
|
45
|
+
end
|
46
|
+
|
35
47
|
attr_reader :rules, :advanced_filter_config
|
36
48
|
|
37
49
|
def initialize(configuration: {}, job_description: {})
|
38
50
|
@configuration = configuration.dup || {}
|
39
51
|
@job_description = job_description&.dup || {}
|
40
52
|
|
41
|
-
|
53
|
+
filtering = Utility::Filtering.extract_filter(@job_description.dig(:connector, :filtering))
|
42
54
|
|
43
|
-
@rules =
|
44
|
-
@advanced_filter_config =
|
55
|
+
@rules = filtering[:rules] || []
|
56
|
+
@advanced_filter_config = filtering[:advanced_snippet] || {}
|
45
57
|
end
|
46
58
|
|
47
59
|
def yield_documents; end
|
@@ -67,16 +79,11 @@ module Connectors
|
|
67
79
|
end
|
68
80
|
|
69
81
|
def filtering_present?
|
70
|
-
@advanced_filter_config.present? || @rules.present?
|
82
|
+
@advanced_filter_config.present? && !@advanced_filter_config.empty? || @rules.present?
|
71
83
|
end
|
72
84
|
|
73
|
-
|
74
|
-
|
75
|
-
def get_filter(filtering)
|
76
|
-
# assume for now, that first object in filtering array or a filter object itself is the only filtering object
|
77
|
-
filter = filtering.is_a?(Array) ? filtering[0] : filtering
|
78
|
-
|
79
|
-
filter.present? ? filter : {}
|
85
|
+
def metadata
|
86
|
+
{}
|
80
87
|
end
|
81
88
|
end
|
82
89
|
end
|
@@ -0,0 +1,42 @@
|
|
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/indifferent_access'
|
9
|
+
require 'active_support/core_ext/object/blank'
|
10
|
+
require 'core/filtering/simple_rule'
|
11
|
+
|
12
|
+
module Connectors
|
13
|
+
module Base
|
14
|
+
class SimpleRulesParser
|
15
|
+
def initialize(rules)
|
16
|
+
@rules = (rules || []).map(&:with_indifferent_access).filter { |r| r[:id] != 'DEFAULT' }.sort_by { |r| r[:order] }
|
17
|
+
end
|
18
|
+
|
19
|
+
def parse
|
20
|
+
merge_rules(@rules.map do |rule_hash|
|
21
|
+
rule = Core::Filtering::SimpleRule.new(rule_hash)
|
22
|
+
unless rule.is_include? || rule.is_exclude?
|
23
|
+
raise "Unknown policy: #{rule.policy}"
|
24
|
+
end
|
25
|
+
parse_rule(rule)
|
26
|
+
end)
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
# merge all rules into a filter object or array
|
32
|
+
# in a base case, does no transformations
|
33
|
+
def merge_rules(rules)
|
34
|
+
rules || []
|
35
|
+
end
|
36
|
+
|
37
|
+
def parse_rule(_rule)
|
38
|
+
raise 'Not implemented'
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -7,6 +7,7 @@
|
|
7
7
|
# frozen_string_literal: true
|
8
8
|
|
9
9
|
require 'connectors/base/connector'
|
10
|
+
require 'core/filtering/validation_status'
|
10
11
|
require 'utility'
|
11
12
|
|
12
13
|
module Connectors
|
@@ -45,6 +46,20 @@ module Connectors
|
|
45
46
|
# raise 'something went wrong'
|
46
47
|
end
|
47
48
|
|
49
|
+
def self.validate_filtering(filtering = {})
|
50
|
+
# TODO: real filtering validation will follow later
|
51
|
+
errors = [
|
52
|
+
{
|
53
|
+
:ids => ['missing-implementation'],
|
54
|
+
:messages => ['Filtering is not implemented yet for the example connector']
|
55
|
+
}
|
56
|
+
]
|
57
|
+
|
58
|
+
return { :state => Core::Filtering::ValidationStatus::INVALID, :errors => errors } if filtering.present?
|
59
|
+
|
60
|
+
{ :state => Core::Filtering::ValidationStatus::VALID, :errors => [] }
|
61
|
+
end
|
62
|
+
|
48
63
|
def yield_documents
|
49
64
|
attachments = [
|
50
65
|
load_attachment('first_attachment.txt'),
|
@@ -11,7 +11,7 @@ require 'connectors/base/connector'
|
|
11
11
|
require 'connectors/gitlab/extractor'
|
12
12
|
require 'connectors/gitlab/custom_client'
|
13
13
|
require 'connectors/gitlab/adapter'
|
14
|
-
require 'core/
|
14
|
+
require 'core/ingestion'
|
15
15
|
|
16
16
|
module Connectors
|
17
17
|
module GitLab
|
@@ -36,6 +36,20 @@ module Connectors
|
|
36
36
|
}
|
37
37
|
end
|
38
38
|
|
39
|
+
def self.validate_filtering(filtering = {})
|
40
|
+
# TODO: real filtering validation will follow later
|
41
|
+
errors = [
|
42
|
+
{
|
43
|
+
:ids => ['missing-implementation'],
|
44
|
+
:messages => ['Filtering is not implemented yet for the GitLab connector']
|
45
|
+
}
|
46
|
+
]
|
47
|
+
|
48
|
+
return { :state => Core::Filtering::ValidationStatus::INVALID, :errors => errors } if filtering.present?
|
49
|
+
|
50
|
+
{ :state => Core::Filtering::ValidationStatus::VALID, :errors => [] }
|
51
|
+
end
|
52
|
+
|
39
53
|
def initialize(configuration: {}, job_description: {})
|
40
54
|
super
|
41
55
|
|
@@ -7,6 +7,8 @@
|
|
7
7
|
# frozen_string_literal: true
|
8
8
|
|
9
9
|
require 'connectors/base/connector'
|
10
|
+
require 'core/filtering/validation_status'
|
11
|
+
require 'connectors/mongodb/mongo_rules_parser'
|
10
12
|
require 'mongo'
|
11
13
|
require 'utility'
|
12
14
|
|
@@ -28,27 +30,46 @@ module Connectors
|
|
28
30
|
|
29
31
|
def self.configurable_fields
|
30
32
|
{
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
33
|
+
:host => {
|
34
|
+
:label => 'Server Hostname'
|
35
|
+
},
|
36
|
+
:user => {
|
37
|
+
:label => 'Username'
|
38
|
+
},
|
39
|
+
:password => {
|
40
|
+
:label => 'Password'
|
41
|
+
},
|
42
|
+
:database => {
|
43
|
+
:label => 'Database'
|
44
|
+
},
|
45
|
+
:collection => {
|
46
|
+
:label => 'Collection'
|
47
|
+
},
|
48
|
+
:direct_connection => {
|
49
|
+
:label => 'Direct connection? (true/false)'
|
50
|
+
}
|
49
51
|
}
|
50
52
|
end
|
51
53
|
|
54
|
+
def self.validate_filtering(filtering = {})
|
55
|
+
valid_filtering = { :state => Core::Filtering::ValidationStatus::VALID, :errors => [] }
|
56
|
+
|
57
|
+
return valid_filtering unless filtering.present?
|
58
|
+
|
59
|
+
filter = Utility::Filtering.extract_filter(filtering)
|
60
|
+
|
61
|
+
advanced_filter_config = filter[:advanced_snippet] || {}
|
62
|
+
filter_keys = advanced_filter_config&.keys
|
63
|
+
|
64
|
+
if !filter_keys&.empty? && (filter_keys.size != 1 || !ALLOWED_TOP_LEVEL_FILTER_KEYS.include?(filter_keys[0]&.to_s))
|
65
|
+
return { :state => Core::Filtering::ValidationStatus::INVALID,
|
66
|
+
:errors => [{ :ids => ['wrong-keys'],
|
67
|
+
:messages => ["Only one of #{ALLOWED_TOP_LEVEL_FILTER_KEYS} is allowed in the filtering object. Keys present: '#{filter_keys}'."] }] }
|
68
|
+
end
|
69
|
+
|
70
|
+
valid_filtering
|
71
|
+
end
|
72
|
+
|
52
73
|
def initialize(configuration: {}, job_description: {})
|
53
74
|
super
|
54
75
|
|
@@ -61,8 +82,6 @@ module Connectors
|
|
61
82
|
end
|
62
83
|
|
63
84
|
def yield_documents
|
64
|
-
check_filtering
|
65
|
-
|
66
85
|
with_client do |client|
|
67
86
|
# We do paging using skip().limit() here to make Ruby recycle the memory for each page pulled from the server after it's not needed any more.
|
68
87
|
# This gives us more control on the usage of the memory (we can adjust PAGE_SIZE constant for that to decrease max memory consumption).
|
@@ -89,6 +108,7 @@ module Connectors
|
|
89
108
|
loop do
|
90
109
|
found_in_page = 0
|
91
110
|
|
111
|
+
Utility::Logger.info("Requesting #{PAGE_SIZE} documents from MongoDB (Starting at #{skip})")
|
92
112
|
view = cursor.skip(skip).limit(PAGE_SIZE)
|
93
113
|
view.each do |document|
|
94
114
|
yield serialize(document)
|
@@ -117,20 +137,9 @@ module Connectors
|
|
117
137
|
|
118
138
|
return create_aggregate_cursor(collection) if @advanced_filter_config[:aggregate].present?
|
119
139
|
|
120
|
-
collection.
|
121
|
-
end
|
122
|
-
|
123
|
-
def check_filtering
|
124
|
-
return unless filtering_present?
|
140
|
+
return create_simple_rules_cursor(collection) if @rules.present?
|
125
141
|
|
126
|
-
|
127
|
-
end
|
128
|
-
|
129
|
-
def check_find_and_aggregate
|
130
|
-
if @advanced_filter_config.keys.size != 1
|
131
|
-
invalid_keys_msg = "Only one of #{ALLOWED_TOP_LEVEL_FILTER_KEYS} is allowed in the filtering object. Keys present: '#{@advanced_filter_config.keys}'."
|
132
|
-
raise Utility::InvalidFilterConfigError.new(invalid_keys_msg)
|
133
|
-
end
|
142
|
+
collection.find
|
134
143
|
end
|
135
144
|
|
136
145
|
def create_aggregate_cursor(collection)
|
@@ -159,6 +168,16 @@ module Connectors
|
|
159
168
|
[collection.find(filter, options), options]
|
160
169
|
end
|
161
170
|
|
171
|
+
def create_simple_rules_cursor(collection)
|
172
|
+
filter = {}
|
173
|
+
if @rules.present?
|
174
|
+
parser = MongoRulesParser.new(@rules)
|
175
|
+
filter = parser.parse
|
176
|
+
end
|
177
|
+
Utility::Logger.info("Filtering with simple rules filter: #{filter}")
|
178
|
+
filter.present? ? collection.find(filter) : collection.find
|
179
|
+
end
|
180
|
+
|
162
181
|
def extract_options(mongodb_function)
|
163
182
|
mongodb_function[:options].present? ? mongodb_function[:options] : {}
|
164
183
|
end
|
@@ -173,9 +192,9 @@ module Connectors
|
|
173
192
|
raise "Invalid value for 'Direct connection' : #{@direct_connection}." unless %w[true false].include?(@direct_connection.to_s.strip.downcase)
|
174
193
|
|
175
194
|
args = {
|
176
|
-
|
177
|
-
|
178
|
-
|
195
|
+
database: @database,
|
196
|
+
direct_connection: to_boolean(@direct_connection)
|
197
|
+
}
|
179
198
|
|
180
199
|
if @user.present? || @password.present?
|
181
200
|
args[:user] = @user
|
@@ -0,0 +1,81 @@
|
|
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 'active_support/core_ext/object'
|
10
|
+
require 'connectors/base/simple_rules_parser'
|
11
|
+
require 'core/filtering/simple_rule'
|
12
|
+
|
13
|
+
module Connectors
|
14
|
+
module MongoDB
|
15
|
+
class MongoRulesParser < Connectors::Base::SimpleRulesParser
|
16
|
+
def parse_rule(rule)
|
17
|
+
field = rule.field
|
18
|
+
value = rule.value
|
19
|
+
unless value.present?
|
20
|
+
raise "value is required for field: #{field}"
|
21
|
+
end
|
22
|
+
unless field.present?
|
23
|
+
raise "field is required for rule: #{rule}"
|
24
|
+
end
|
25
|
+
op = rule.rule
|
26
|
+
case op
|
27
|
+
when Core::Filtering::SimpleRule::Rule::EQUALS
|
28
|
+
parse_equals(rule)
|
29
|
+
when Core::Filtering::SimpleRule::Rule::GREATER_THAN
|
30
|
+
parse_greater_than(rule)
|
31
|
+
when Core::Filtering::SimpleRule::Rule::LESS_THAN
|
32
|
+
parse_less_than(rule)
|
33
|
+
when Core::Filtering::SimpleRule::Rule::REGEX
|
34
|
+
parse_regex(rule)
|
35
|
+
else
|
36
|
+
raise "Unknown operator: #{op}"
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def merge_rules(rules)
|
41
|
+
return {} if rules.empty?
|
42
|
+
return rules[0] if rules.size == 1
|
43
|
+
{ '$and' => rules }
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
def parse_equals(rule)
|
49
|
+
if rule.is_include?
|
50
|
+
{ rule.field => rule.value }
|
51
|
+
else
|
52
|
+
{ rule.field => { '$ne' => rule.value } }
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def parse_greater_than(rule)
|
57
|
+
if rule.is_include?
|
58
|
+
{ rule.field => { '$gt' => rule.value } }
|
59
|
+
else
|
60
|
+
{ rule.field => { '$lte' => rule.value } }
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def parse_less_than(rule)
|
65
|
+
if rule.is_include?
|
66
|
+
{ rule.field => { '$lt' => rule.value } }
|
67
|
+
else
|
68
|
+
{ rule.field => { '$gte' => rule.value } }
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def parse_regex(rule)
|
73
|
+
if rule.is_include?
|
74
|
+
{ rule.field => /#{rule.value}/ }
|
75
|
+
else
|
76
|
+
{ rule.field => { '$not' => /#{rule.value}/ } }
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
data/lib/core/configuration.rb
CHANGED
@@ -24,8 +24,10 @@ 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
|
doc = {
|
28
|
-
:configuration => configuration
|
29
|
+
:configuration => configuration,
|
30
|
+
:features => features
|
29
31
|
}
|
30
32
|
|
31
33
|
doc[:service_type] = service_type if service_type && connector_settings.needs_service_type?
|
@@ -0,0 +1,137 @@
|
|
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 'active_support/core_ext/hash/indifferent_access'
|
10
|
+
require 'connectors/sync_status'
|
11
|
+
require 'core/elastic_connector_actions'
|
12
|
+
require 'utility'
|
13
|
+
|
14
|
+
module Core
|
15
|
+
class ConnectorJob
|
16
|
+
DEFAULT_PAGE_SIZE = 100
|
17
|
+
|
18
|
+
# Error Classes
|
19
|
+
class ConnectorJobNotFoundError < StandardError; end
|
20
|
+
|
21
|
+
def self.fetch_by_id(job_id)
|
22
|
+
es_response = ElasticConnectorActions.get_job(job_id)
|
23
|
+
|
24
|
+
raise ConnectorJobNotFoundError.new("Connector job with id=#{job_id} was not found.") unless es_response[:found]
|
25
|
+
new(es_response)
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.pending_jobs(page_size = DEFAULT_PAGE_SIZE)
|
29
|
+
query = { terms: { status: Connectors::SyncStatus::PENDING_STATUES } }
|
30
|
+
fetch_jobs_by_query(query, page_size)
|
31
|
+
end
|
32
|
+
|
33
|
+
def self.orphaned_jobs(_page_size = DEFAULT_PAGE_SIZE)
|
34
|
+
[]
|
35
|
+
end
|
36
|
+
|
37
|
+
def self.stuck_jobs(_page_size = DEFAULT_PAGE_SIZE)
|
38
|
+
[]
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.enqueue(_connector_id)
|
42
|
+
nil
|
43
|
+
end
|
44
|
+
|
45
|
+
def id
|
46
|
+
@elasticsearch_response[:_id]
|
47
|
+
end
|
48
|
+
|
49
|
+
def [](property_name)
|
50
|
+
@elasticsearch_response[:_source][property_name]
|
51
|
+
end
|
52
|
+
|
53
|
+
def status
|
54
|
+
self[:status]
|
55
|
+
end
|
56
|
+
|
57
|
+
def in_progress?
|
58
|
+
status == Connectors::SyncStatus::IN_PROGRESS
|
59
|
+
end
|
60
|
+
|
61
|
+
def canceling?
|
62
|
+
status == Connectors::SyncStatus::CANCELING
|
63
|
+
end
|
64
|
+
|
65
|
+
def connector_snapshot
|
66
|
+
self[:connector]
|
67
|
+
end
|
68
|
+
|
69
|
+
def connector_id
|
70
|
+
connector_snapshot[:id]
|
71
|
+
end
|
72
|
+
|
73
|
+
def index_name
|
74
|
+
connector_snapshot[:configuration]
|
75
|
+
end
|
76
|
+
|
77
|
+
def language
|
78
|
+
connector_snapshot[:language]
|
79
|
+
end
|
80
|
+
|
81
|
+
def service_type
|
82
|
+
connector_snapshot[:service_type]
|
83
|
+
end
|
84
|
+
|
85
|
+
def configuration
|
86
|
+
connector_snapshot[:configuration]
|
87
|
+
end
|
88
|
+
|
89
|
+
def filtering
|
90
|
+
Utility::Filtering.extract_filter(connector_snapshot[:filtering])
|
91
|
+
end
|
92
|
+
|
93
|
+
def pipeline
|
94
|
+
connector_snapshot[:pipeline]
|
95
|
+
end
|
96
|
+
|
97
|
+
def connector
|
98
|
+
@connector ||= ConnectorSettings.fetch_by_id(connector_id)
|
99
|
+
end
|
100
|
+
|
101
|
+
def reload_connector!
|
102
|
+
@connector = nil
|
103
|
+
connector
|
104
|
+
end
|
105
|
+
|
106
|
+
def reload
|
107
|
+
es_response = ElasticConnectorActions.get_job(id)
|
108
|
+
raise ConnectorJobNotFoundError.new("Connector job with id=#{id} was not found.") unless es_response[:found]
|
109
|
+
# TODO: remove the usage of with_indifferent_access. get_id method is expected to return a hash
|
110
|
+
@elasticsearch_response = es_response.with_indifferent_access
|
111
|
+
@connector = nil
|
112
|
+
end
|
113
|
+
|
114
|
+
private
|
115
|
+
|
116
|
+
def initialize(es_response)
|
117
|
+
# TODO: remove the usage of with_indifferent_access. The initialize method should expect a hash argument
|
118
|
+
@elasticsearch_response = es_response.with_indifferent_access
|
119
|
+
end
|
120
|
+
|
121
|
+
def self.fetch_jobs_by_query(query, page_size)
|
122
|
+
results = []
|
123
|
+
offset = 0
|
124
|
+
loop do
|
125
|
+
response = ElasticConnectorActions.search_jobs(query, page_size, offset)
|
126
|
+
|
127
|
+
hits = response.dig('hits', 'hits') || []
|
128
|
+
total = response.dig('hits', 'total', 'value') || 0
|
129
|
+
results += hits.map { |hit| new(hit) }
|
130
|
+
break if results.size >= total
|
131
|
+
offset += hits.size
|
132
|
+
end
|
133
|
+
|
134
|
+
results
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|