connectors_service 8.6.0.4 → 8.7.0.0.pre.20221117T004928Z
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 +9 -8
- data/lib/app/app.rb +4 -0
- data/lib/app/config.rb +3 -0
- data/lib/app/dispatcher.rb +44 -17
- data/lib/app/preflight_check.rb +11 -0
- data/lib/connectors/base/advanced_snippet_against_schema_validator.rb +173 -0
- data/lib/connectors/base/advanced_snippet_validator.rb +34 -0
- data/lib/connectors/base/connector.rb +43 -14
- data/lib/connectors/base/simple_rules_parser.rb +42 -0
- data/lib/connectors/example/connector.rb +6 -0
- data/lib/connectors/example/example_advanced_snippet_validator.rb +35 -0
- data/lib/connectors/gitlab/connector.rb +6 -1
- data/lib/connectors/gitlab/gitlab_advanced_snippet_validator.rb +35 -0
- data/lib/connectors/mongodb/connector.rb +47 -43
- data/lib/connectors/mongodb/mongo_advanced_snippet_against_schema_validator.rb +22 -0
- data/lib/connectors/mongodb/mongo_advanced_snippet_schema.rb +292 -0
- data/lib/connectors/mongodb/mongo_rules_parser.rb +81 -0
- data/lib/connectors/sync_status.rb +6 -1
- data/lib/connectors/tolerable_error_helper.rb +43 -0
- data/lib/connectors_app/// +13 -0
- data/lib/core/configuration.rb +3 -1
- data/lib/core/connector_job.rb +210 -0
- data/lib/core/connector_settings.rb +52 -16
- data/lib/core/elastic_connector_actions.rb +320 -59
- 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 +118 -0
- data/lib/core/{output_sink.rb → ingestion.rb} +1 -5
- data/lib/core/jobs/consumer.rb +132 -0
- data/lib/core/jobs/producer.rb +26 -0
- data/lib/core/scheduler.rb +40 -10
- data/lib/core/single_scheduler.rb +1 -1
- data/lib/core/sync_job_runner.rb +80 -16
- data/lib/core.rb +4 -0
- data/lib/utility/bulk_queue.rb +87 -0
- data/lib/utility/constants.rb +7 -0
- data/lib/utility/error_monitor.rb +108 -0
- data/lib/utility/errors.rb +0 -12
- data/lib/utility/filtering.rb +22 -0
- data/lib/utility/logger.rb +1 -1
- data/lib/utility.rb +11 -4
- metadata +31 -12
- 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: 39e0bb6ae283bcb1afebd57e715342b7e30a7178c33653dfd05fee6f322061e6
|
4
|
+
data.tar.gz: e1fd33b8d28d7d906b4b8eea672a34c0be12e0a124977bbeb10c62c147690a8b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: bb6b96ac0c07e9ed4cbe87f16a17844771c12d7a7ccbb2e8c7aeb571cb146f67e5a0489b1e73ff907613dcda442912ba09c193e0e8e4e11fec553ebe9afdec12
|
7
|
+
data.tar.gz: c3c2fc12ac9de6ed37a85dbfb46725e2de37c08beba453c3e9d8a3f0c6c56a078a1c5987931251ca84f18e5a9388b6698ff43abe3ce547d291537b3040b15b0a
|
data/config/connectors.yml
CHANGED
@@ -1,10 +1,11 @@
|
|
1
1
|
# general metadata
|
2
|
-
version: 8.
|
3
|
-
repository:
|
4
|
-
revision:
|
2
|
+
version: 8.7.0.0-20221117T004928Z
|
3
|
+
repository: https://github.com/elastic/connectors-ruby.git
|
4
|
+
revision: 294214a26b0fe9a4347763b01de681c336e8daae
|
5
5
|
elasticsearch:
|
6
|
+
cloud_id: CHANGEME
|
6
7
|
hosts: http://localhost:9200
|
7
|
-
api_key:
|
8
|
+
api_key: CHANGEME
|
8
9
|
retry_on_failure: 3
|
9
10
|
request_timeout: 120
|
10
11
|
disable_warnings: true
|
@@ -15,10 +16,10 @@ thread_pool:
|
|
15
16
|
max_threads: 5
|
16
17
|
max_queue: 100
|
17
18
|
log_level: info
|
18
|
-
ecs_logging:
|
19
|
+
ecs_logging: true
|
19
20
|
poll_interval: 3
|
20
21
|
termination_timeout: 60
|
21
22
|
heartbeat_interval: 1800
|
22
|
-
native_mode:
|
23
|
-
connector_id:
|
24
|
-
service_type:
|
23
|
+
native_mode: true
|
24
|
+
connector_id: CHANGEME
|
25
|
+
service_type: CHANGEME
|
data/lib/app/app.rb
CHANGED
@@ -17,6 +17,10 @@ require 'utility/logger'
|
|
17
17
|
module App
|
18
18
|
Utility::Environment.set_execution_environment(App::Config) do
|
19
19
|
App::PreflightCheck.run!
|
20
|
+
|
21
|
+
# set exit hook
|
22
|
+
Kernel.at_exit { App::Dispatcher.shutdown! }
|
23
|
+
|
20
24
|
App::Dispatcher.start!
|
21
25
|
rescue App::PreflightCheck::CheckFailure => e
|
22
26
|
Utility::Logger.error("Preflight check failed: #{e.message}")
|
data/lib/app/config.rb
CHANGED
@@ -54,6 +54,9 @@ puts "Parsing #{CONFIG_FILE} configuration file."
|
|
54
54
|
optional(:poll_interval).value(:integer)
|
55
55
|
optional(:termination_timeout).value(:integer)
|
56
56
|
optional(:heartbeat_interval).value(:integer)
|
57
|
+
|
58
|
+
optional(:max_ingestion_queue_size).value(:integer) # items
|
59
|
+
optional(:max_ingestion_queue_bytes).value(:integer) # bytes
|
57
60
|
end
|
58
61
|
end
|
59
62
|
|
data/lib/app/dispatcher.rb
CHANGED
@@ -28,6 +28,10 @@ module App
|
|
28
28
|
def start!
|
29
29
|
running!
|
30
30
|
Utility::Logger.info("Starting connector service in #{App::Config.native_mode ? 'native' : 'non-native'} mode...")
|
31
|
+
|
32
|
+
# start sync jobs consumer
|
33
|
+
start_consumer!
|
34
|
+
|
31
35
|
start_polling_jobs!
|
32
36
|
end
|
33
37
|
|
@@ -37,6 +41,8 @@ module App
|
|
37
41
|
scheduler.shutdown
|
38
42
|
pool.shutdown
|
39
43
|
pool.wait_for_termination(TERMINATION_TIMEOUT)
|
44
|
+
|
45
|
+
stop_consumer!
|
40
46
|
end
|
41
47
|
|
42
48
|
private
|
@@ -68,11 +74,16 @@ module App
|
|
68
74
|
scheduler.when_triggered do |connector_settings, task|
|
69
75
|
case task
|
70
76
|
when :sync
|
71
|
-
|
77
|
+
# update connector sync_now flag
|
78
|
+
Core::ElasticConnectorActions.update_connector_sync_now(connector_settings.id, false)
|
79
|
+
|
80
|
+
Core::Jobs::Producer.enqueue_job(job_type: :sync, connector_settings: connector_settings)
|
72
81
|
when :heartbeat
|
73
82
|
start_heartbeat_task(connector_settings)
|
74
83
|
when :configuration
|
75
84
|
start_configuration_task(connector_settings)
|
85
|
+
when :filter_validation
|
86
|
+
start_filter_validation_task(connector_settings)
|
76
87
|
else
|
77
88
|
Utility::Logger.error("Unknown task type: #{task}. Skipping...")
|
78
89
|
end
|
@@ -81,22 +92,6 @@ module App
|
|
81
92
|
Utility::ExceptionTracking.log_exception(e, 'The connector service failed due to unexpected error.')
|
82
93
|
end
|
83
94
|
|
84
|
-
def start_sync_task(connector_settings)
|
85
|
-
start_heartbeat_task(connector_settings)
|
86
|
-
pool.post do
|
87
|
-
Utility::Logger.info("Initiating a sync job for #{connector_settings.formatted}...")
|
88
|
-
Core::ElasticConnectorActions.ensure_content_index_exists(connector_settings.index_name)
|
89
|
-
job_runner = Core::SyncJobRunner.new(connector_settings)
|
90
|
-
job_runner.execute
|
91
|
-
rescue Core::JobAlreadyRunningError
|
92
|
-
Utility::Logger.info("Sync job for #{connector_settings.formatted} is already running, skipping.")
|
93
|
-
rescue Core::ConnectorVersionChangedError => e
|
94
|
-
Utility::Logger.info("Could not start the job because #{connector_settings.formatted} has been updated externally. Message: #{e.message}")
|
95
|
-
rescue StandardError => e
|
96
|
-
Utility::ExceptionTracking.log_exception(e, "Sync job for #{connector_settings.formatted} failed due to unexpected error.")
|
97
|
-
end
|
98
|
-
end
|
99
|
-
|
100
95
|
def start_heartbeat_task(connector_settings)
|
101
96
|
pool.post do
|
102
97
|
Utility::Logger.info("Sending heartbeat for #{connector_settings.formatted}...")
|
@@ -120,6 +115,38 @@ module App
|
|
120
115
|
Utility::ExceptionTracking.log_exception(e, "Configuration task for #{connector_settings.formatted} failed due to unexpected error.")
|
121
116
|
end
|
122
117
|
end
|
118
|
+
|
119
|
+
def start_filter_validation_task(connector_settings)
|
120
|
+
pool.post do
|
121
|
+
Utility::Logger.info("Validating filters for #{connector_settings.formatted}...")
|
122
|
+
validation_job_runner = Core::Filtering::ValidationJobRunner.new(connector_settings)
|
123
|
+
validation_job_runner.execute
|
124
|
+
rescue StandardError => e
|
125
|
+
Utility::ExceptionTracking.log_exception(e, "Filter validation task for #{connector_settings.formatted} failed due to unexpected error.")
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
def start_consumer!
|
130
|
+
@consumer = Core::Jobs::Consumer.new(
|
131
|
+
poll_interval: POLL_INTERVAL,
|
132
|
+
termination_timeout: TERMINATION_TIMEOUT,
|
133
|
+
min_threads: MIN_THREADS,
|
134
|
+
max_threads: MAX_THREADS,
|
135
|
+
max_queue: MAX_QUEUE,
|
136
|
+
max_ingestion_queue_size: (App::Config.max_ingestion_queue_size || Utility::Constants::DEFAULT_MAX_INGESTION_QUEUE_SIZE).to_i,
|
137
|
+
max_ingestion_queue_bytes: (App::Config.max_ingestion_queue_bytes || Utility::Constants::DEFAULT_MAX_INGESTION_QUEUE_BYTES).to_i,
|
138
|
+
scheduler: scheduler
|
139
|
+
)
|
140
|
+
|
141
|
+
@consumer.subscribe!(index_name: Utility::Constants::JOB_INDEX)
|
142
|
+
end
|
143
|
+
|
144
|
+
def stop_consumer!
|
145
|
+
return if @consumer.nil?
|
146
|
+
return unless @consumer.running?
|
147
|
+
|
148
|
+
@consumer.shutdown!
|
149
|
+
end
|
123
150
|
end
|
124
151
|
end
|
125
152
|
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
|
|
@@ -0,0 +1,173 @@
|
|
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 'connectors/base/advanced_snippet_validator'
|
11
|
+
require 'core/filtering/validation_status'
|
12
|
+
|
13
|
+
module Connectors
|
14
|
+
module Base
|
15
|
+
class AdvancedSnippetAgainstSchemaValidator < Connectors::Base::AdvancedSnippetValidator
|
16
|
+
|
17
|
+
MAX_RECURSION_DEPTH = 50
|
18
|
+
ADVANCED_SNIPPET_ID = 'advanced_snippet'
|
19
|
+
|
20
|
+
def initialize(advanced_snippet, schema)
|
21
|
+
super(advanced_snippet)
|
22
|
+
@schema = schema
|
23
|
+
end
|
24
|
+
|
25
|
+
def is_snippet_valid?
|
26
|
+
validation_result = validate_against_schema(@schema, @advanced_snippet)
|
27
|
+
log_validation_result(validation_result)
|
28
|
+
validation_result
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def validate_against_schema(config_schema, advanced_snippet, recursion_depth = 0)
|
34
|
+
# Prevent unintentional/intentional SystemStackErrors/crashes
|
35
|
+
return unexpected_error if exceeded_recursion_depth?(recursion_depth)
|
36
|
+
|
37
|
+
return valid_snippet if config_schema.nil? || config_schema.empty?
|
38
|
+
|
39
|
+
schema_fields = config_schema[:fields].is_a?(Hash) ? config_schema.dig(:fields, :values) : config_schema[:fields]
|
40
|
+
snippet_field_names = advanced_snippet&.keys&.map(&:to_s)
|
41
|
+
schema_field_names = schema_fields.map { |field| field[:name] }
|
42
|
+
|
43
|
+
return unexpected_field(schema_field_names, snippet_field_names) if unexpected_field_present?(snippet_field_names, schema_field_names)
|
44
|
+
|
45
|
+
return fields_constraint_violation(config_schema[:fields]) if fields_constraints_violated?(config_schema, advanced_snippet)
|
46
|
+
|
47
|
+
schema_fields.each do |field|
|
48
|
+
name = field[:name]
|
49
|
+
type = field[:type]
|
50
|
+
optional = field[:optional] || false
|
51
|
+
|
52
|
+
snippet_field_value = advanced_snippet.with_indifferent_access[name]
|
53
|
+
|
54
|
+
next if optional && (snippet_field_value.nil? || !snippet_field_value.present?)
|
55
|
+
|
56
|
+
return wrong_names(snippet_field_names, name) unless snippet_field_names.include?(name)
|
57
|
+
|
58
|
+
return wrong_type(name, type, snippet_field_value) if type_error_present?(type, snippet_field_value)
|
59
|
+
|
60
|
+
if field[:fields].present?
|
61
|
+
validation_result = validate_against_schema(field, snippet_field_value, recursion_depth + 1)
|
62
|
+
|
63
|
+
return validation_result unless validation_result[:is_valid]
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
valid_snippet
|
68
|
+
end
|
69
|
+
|
70
|
+
def fields_constraints_violated?(config_schema, advanced_snippet)
|
71
|
+
return false unless config_schema[:fields].is_a?(Hash)
|
72
|
+
|
73
|
+
constraints = config_schema.dig(:fields, :constraints)
|
74
|
+
constraints = constraints.is_a?(Array) ? constraints : [constraints]
|
75
|
+
|
76
|
+
constraints.each do |constraint|
|
77
|
+
return true unless constraint.call(advanced_snippet)
|
78
|
+
end
|
79
|
+
|
80
|
+
false
|
81
|
+
end
|
82
|
+
|
83
|
+
def type_error_present?(schema_type, snippet_value)
|
84
|
+
return !schema_type.call(snippet_value) if schema_type.is_a?(Proc)
|
85
|
+
|
86
|
+
!snippet_value.is_a?(schema_type)
|
87
|
+
end
|
88
|
+
|
89
|
+
def exceeded_recursion_depth?(recursion_depth)
|
90
|
+
if recursion_depth >= MAX_RECURSION_DEPTH
|
91
|
+
Utility::Logger.warn("Recursion depth for filtering validation exceeded. (Max recursion depth: #{MAX_RECURSION_DEPTH})")
|
92
|
+
return true
|
93
|
+
end
|
94
|
+
|
95
|
+
false
|
96
|
+
end
|
97
|
+
|
98
|
+
def unexpected_field_present?(actual_field_names, expected_field_names)
|
99
|
+
difference = actual_field_names - expected_field_names
|
100
|
+
|
101
|
+
# we have field names, which we didn't expect
|
102
|
+
!difference.empty?
|
103
|
+
end
|
104
|
+
|
105
|
+
def valid_snippet
|
106
|
+
{
|
107
|
+
:state => Core::Filtering::ValidationStatus::VALID,
|
108
|
+
:errors => []
|
109
|
+
}
|
110
|
+
end
|
111
|
+
|
112
|
+
def unexpected_field(expected_fields, actual_fields)
|
113
|
+
{
|
114
|
+
:state => Core::Filtering::ValidationStatus::INVALID,
|
115
|
+
:errors => [
|
116
|
+
{
|
117
|
+
:ids => [ADVANCED_SNIPPET_ID],
|
118
|
+
:messages => ["Encountered unexpected fields '#{actual_fields}'. Expected: '#{expected_fields}'."]
|
119
|
+
}
|
120
|
+
]
|
121
|
+
}
|
122
|
+
end
|
123
|
+
|
124
|
+
def wrong_type(field_name, expected_type, actual_value)
|
125
|
+
{
|
126
|
+
:state => Core::Filtering::ValidationStatus::INVALID,
|
127
|
+
:errors => [
|
128
|
+
{
|
129
|
+
:ids => [ADVANCED_SNIPPET_ID],
|
130
|
+
:messages => ["Expected field type '#{expected_type.is_a?(Proc) ? 'custom matcher' : expected_type}' for field '#{field_name}', but got value '#{actual_value.inspect}' of type '#{actual_value.class}'."]
|
131
|
+
}
|
132
|
+
]
|
133
|
+
}
|
134
|
+
end
|
135
|
+
|
136
|
+
def wrong_names(actual_field_names, expected_field_name)
|
137
|
+
{
|
138
|
+
:state => Core::Filtering::ValidationStatus::INVALID,
|
139
|
+
:errors => [
|
140
|
+
{
|
141
|
+
:ids => [ADVANCED_SNIPPET_ID],
|
142
|
+
:messages => ["Expected field name '#{expected_field_name}', but got #{actual_field_names}."]
|
143
|
+
}
|
144
|
+
]
|
145
|
+
}
|
146
|
+
end
|
147
|
+
|
148
|
+
def fields_constraint_violation(fields)
|
149
|
+
{
|
150
|
+
:state => Core::Filtering::ValidationStatus::INVALID,
|
151
|
+
:errors => [
|
152
|
+
{
|
153
|
+
:ids => [ADVANCED_SNIPPET_ID],
|
154
|
+
:messages => ["A fields constraint was violated for fields: '#{fields[:values].map { |v| v[:name] }}'. Check advanced snippet field constraints."]
|
155
|
+
}
|
156
|
+
]
|
157
|
+
}
|
158
|
+
end
|
159
|
+
|
160
|
+
def unexpected_error
|
161
|
+
{
|
162
|
+
:state => Core::Filtering::ValidationStatus::INVALID,
|
163
|
+
:errors => [
|
164
|
+
{
|
165
|
+
:ids => [ADVANCED_SNIPPET_ID],
|
166
|
+
:messages => ['Unexpected error. Check logs for details.']
|
167
|
+
}
|
168
|
+
]
|
169
|
+
}
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
173
|
+
end
|
@@ -0,0 +1,34 @@
|
|
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 Connectors
|
11
|
+
module Base
|
12
|
+
class AdvancedSnippetValidator
|
13
|
+
|
14
|
+
def initialize(advanced_snippet)
|
15
|
+
@advanced_snippet = advanced_snippet || {}
|
16
|
+
end
|
17
|
+
|
18
|
+
def is_snippet_valid?
|
19
|
+
raise 'Advanced Snippet validation not implemented'
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def log_validation_result(validation_result)
|
25
|
+
Utility::Logger.info("Filtering Advanced Configuration validation result: #{validation_result[:state]}")
|
26
|
+
if validation_result[:errors].present?
|
27
|
+
validation_result[:errors].each do |error|
|
28
|
+
Utility::Logger.warn("Validation error for: '#{error[:ids]}': '#{error[:messages]}'")
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -6,11 +6,15 @@
|
|
6
6
|
|
7
7
|
# frozen_string_literal: true
|
8
8
|
|
9
|
+
require 'active_support/core_ext/hash/indifferent_access'
|
10
|
+
require 'app/config'
|
9
11
|
require 'bson'
|
10
|
-
require '
|
12
|
+
require 'connectors/base/advanced_snippet_validator'
|
13
|
+
require 'core/ingestion'
|
14
|
+
require 'connectors/tolerable_error_helper'
|
15
|
+
require 'core/filtering/validation_status'
|
11
16
|
require 'utility'
|
12
|
-
require '
|
13
|
-
require 'active_support/core_ext/hash/indifferent_access'
|
17
|
+
require 'utility/filtering'
|
14
18
|
|
15
19
|
module Connectors
|
16
20
|
module Base
|
@@ -32,20 +36,50 @@ module Connectors
|
|
32
36
|
raise 'Not implemented for this connector'
|
33
37
|
end
|
34
38
|
|
39
|
+
def self.kibana_features
|
40
|
+
[
|
41
|
+
Utility::Constants::FILTERING_RULES_FEATURE,
|
42
|
+
Utility::Constants::FILTERING_ADVANCED_FEATURE
|
43
|
+
]
|
44
|
+
end
|
45
|
+
|
46
|
+
def self.advanced_snippet_validator
|
47
|
+
AdvancedSnippetValidator
|
48
|
+
end
|
49
|
+
|
50
|
+
def self.validate_filtering(filtering = {})
|
51
|
+
# nothing to validate
|
52
|
+
return { :state => Core::Filtering::ValidationStatus::VALID, :errors => [] } unless filtering.present?
|
53
|
+
|
54
|
+
filter = Utility::Filtering.extract_filter(filtering)
|
55
|
+
advanced_snippet = filter.dig(:advanced_snippet, :value)
|
56
|
+
|
57
|
+
snippet_validator_instance = advanced_snippet_validator.new(advanced_snippet)
|
58
|
+
|
59
|
+
snippet_validator_instance.is_snippet_valid?
|
60
|
+
end
|
61
|
+
|
35
62
|
attr_reader :rules, :advanced_filter_config
|
36
63
|
|
37
64
|
def initialize(configuration: {}, job_description: {})
|
65
|
+
error_monitor = Utility::ErrorMonitor.new
|
66
|
+
@tolerable_error_helper = Connectors::TolerableErrorHelper.new(error_monitor)
|
67
|
+
|
38
68
|
@configuration = configuration.dup || {}
|
39
69
|
@job_description = job_description&.dup || {}
|
40
70
|
|
41
|
-
|
71
|
+
filtering = Utility::Filtering.extract_filter(@job_description.dig(:connector, :filtering))
|
42
72
|
|
43
|
-
@rules =
|
44
|
-
@advanced_filter_config =
|
73
|
+
@rules = filtering[:rules] || []
|
74
|
+
@advanced_filter_config = filtering.dig(:advanced_snippet, :value) || {}
|
45
75
|
end
|
46
76
|
|
47
77
|
def yield_documents; end
|
48
78
|
|
79
|
+
def yield_with_handling_tolerable_errors(identifier: nil, &block)
|
80
|
+
@tolerable_error_helper.yield_single_document(identifier: identifier, &block)
|
81
|
+
end
|
82
|
+
|
49
83
|
def do_health_check
|
50
84
|
raise 'Not implemented for this connector'
|
51
85
|
end
|
@@ -67,16 +101,11 @@ module Connectors
|
|
67
101
|
end
|
68
102
|
|
69
103
|
def filtering_present?
|
70
|
-
@advanced_filter_config.present? || @rules.present?
|
104
|
+
@advanced_filter_config.present? && !@advanced_filter_config.empty? || @rules.present?
|
71
105
|
end
|
72
106
|
|
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 : {}
|
107
|
+
def metadata
|
108
|
+
{}
|
80
109
|
end
|
81
110
|
end
|
82
111
|
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,8 @@
|
|
7
7
|
# frozen_string_literal: true
|
8
8
|
|
9
9
|
require 'connectors/base/connector'
|
10
|
+
require 'connectors/example/example_advanced_snippet_validator'
|
11
|
+
require 'core/filtering/validation_status'
|
10
12
|
require 'utility'
|
11
13
|
|
12
14
|
module Connectors
|
@@ -45,6 +47,10 @@ module Connectors
|
|
45
47
|
# raise 'something went wrong'
|
46
48
|
end
|
47
49
|
|
50
|
+
def self.advanced_snippet_validator
|
51
|
+
ExampleAdvancedSnippetValidator
|
52
|
+
end
|
53
|
+
|
48
54
|
def yield_documents
|
49
55
|
attachments = [
|
50
56
|
load_attachment('first_attachment.txt'),
|
@@ -0,0 +1,35 @@
|
|
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 'connectors/base/advanced_snippet_validator'
|
10
|
+
|
11
|
+
module Connectors
|
12
|
+
module Example
|
13
|
+
class ExampleAdvancedSnippetValidator < Connectors::Base::AdvancedSnippetValidator
|
14
|
+
|
15
|
+
def is_snippet_valid?
|
16
|
+
# TODO: real filtering validation will follow later
|
17
|
+
errors = [
|
18
|
+
{
|
19
|
+
:ids => ['missing-implementation'],
|
20
|
+
:messages => ['Filtering is not implemented yet for the example connector']
|
21
|
+
}
|
22
|
+
]
|
23
|
+
|
24
|
+
validation_result = if @advanced_snippet.present? && !@advanced_snippet.empty?
|
25
|
+
{ :state => Core::Filtering::ValidationStatus::INVALID, :errors => errors }
|
26
|
+
else
|
27
|
+
{ :state => Core::Filtering::ValidationStatus::VALID, :errors => [] }
|
28
|
+
end
|
29
|
+
log_validation_result(validation_result)
|
30
|
+
validation_result
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -11,7 +11,8 @@ 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 '
|
14
|
+
require 'connectors/gitlab/gitlab_advanced_snippet_validator'
|
15
|
+
require 'core/ingestion'
|
15
16
|
|
16
17
|
module Connectors
|
17
18
|
module GitLab
|
@@ -36,6 +37,10 @@ module Connectors
|
|
36
37
|
}
|
37
38
|
end
|
38
39
|
|
40
|
+
def self.advanced_snippet_validator
|
41
|
+
GitLabAdvancedSnippetValidator
|
42
|
+
end
|
43
|
+
|
39
44
|
def initialize(configuration: {}, job_description: {})
|
40
45
|
super
|
41
46
|
|
@@ -0,0 +1,35 @@
|
|
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 'connectors/base/advanced_snippet_validator'
|
10
|
+
|
11
|
+
module Connectors
|
12
|
+
module GitLab
|
13
|
+
class GitLabAdvancedSnippetValidator < Connectors::Base::AdvancedSnippetValidator
|
14
|
+
|
15
|
+
def is_snippet_valid?
|
16
|
+
# TODO: real filtering validation will follow later
|
17
|
+
errors = [
|
18
|
+
{
|
19
|
+
:ids => ['missing-implementation'],
|
20
|
+
:messages => ['Filtering is not implemented yet for the GitLab connector']
|
21
|
+
}
|
22
|
+
]
|
23
|
+
|
24
|
+
validation_result = if @advanced_snippet.present? && !@advanced_snippet.empty?
|
25
|
+
{ :state => Core::Filtering::ValidationStatus::INVALID, :errors => errors }
|
26
|
+
else
|
27
|
+
{ :state => Core::Filtering::ValidationStatus::VALID, :errors => [] }
|
28
|
+
end
|
29
|
+
log_validation_result(validation_result)
|
30
|
+
validation_result
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|