connectors_service 8.5.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/LICENSE +93 -0
- data/NOTICE.txt +2 -0
- data/bin/connectors_service +4 -0
- data/bin/list_connectors +4 -0
- data/config/connectors.yml +25 -0
- data/lib/app/app.rb +25 -0
- data/lib/app/config.rb +132 -0
- data/lib/app/console_app.rb +278 -0
- data/lib/app/dispatcher.rb +121 -0
- data/lib/app/menu.rb +104 -0
- data/lib/app/preflight_check.rb +134 -0
- data/lib/app/version.rb +10 -0
- data/lib/connectors/base/adapter.rb +119 -0
- data/lib/connectors/base/connector.rb +57 -0
- data/lib/connectors/base/custom_client.rb +111 -0
- data/lib/connectors/connector_status.rb +31 -0
- data/lib/connectors/crawler/scheduler.rb +32 -0
- data/lib/connectors/example/connector.rb +57 -0
- data/lib/connectors/example/example_attachments/first_attachment.txt +1 -0
- data/lib/connectors/example/example_attachments/second_attachment.txt +1 -0
- data/lib/connectors/example/example_attachments/third_attachment.txt +1 -0
- data/lib/connectors/gitlab/adapter.rb +50 -0
- data/lib/connectors/gitlab/connector.rb +67 -0
- data/lib/connectors/gitlab/custom_client.rb +44 -0
- data/lib/connectors/gitlab/extractor.rb +69 -0
- data/lib/connectors/mongodb/connector.rb +138 -0
- data/lib/connectors/registry.rb +52 -0
- data/lib/connectors/sync_status.rb +21 -0
- data/lib/connectors.rb +16 -0
- data/lib/connectors_app/// +13 -0
- data/lib/connectors_service.rb +24 -0
- data/lib/connectors_utility.rb +16 -0
- data/lib/core/configuration.rb +48 -0
- data/lib/core/connector_settings.rb +142 -0
- data/lib/core/elastic_connector_actions.rb +269 -0
- data/lib/core/heartbeat.rb +32 -0
- data/lib/core/native_scheduler.rb +24 -0
- data/lib/core/output_sink/base_sink.rb +33 -0
- data/lib/core/output_sink/combined_sink.rb +38 -0
- data/lib/core/output_sink/console_sink.rb +51 -0
- data/lib/core/output_sink/es_sink.rb +74 -0
- data/lib/core/output_sink.rb +13 -0
- data/lib/core/scheduler.rb +158 -0
- data/lib/core/single_scheduler.rb +29 -0
- data/lib/core/sync_job_runner.rb +111 -0
- data/lib/core.rb +16 -0
- data/lib/list_connectors.rb +22 -0
- data/lib/stubs/app_config.rb +35 -0
- data/lib/stubs/connectors/stats.rb +35 -0
- data/lib/stubs/service_type.rb +13 -0
- data/lib/utility/constants.rb +20 -0
- data/lib/utility/cron.rb +81 -0
- data/lib/utility/elasticsearch/index/language_data.yml +111 -0
- data/lib/utility/elasticsearch/index/mappings.rb +104 -0
- data/lib/utility/elasticsearch/index/text_analysis_settings.rb +226 -0
- data/lib/utility/environment.rb +33 -0
- data/lib/utility/errors.rb +132 -0
- data/lib/utility/es_client.rb +84 -0
- data/lib/utility/exception_tracking.rb +64 -0
- data/lib/utility/extension_mapping_util.rb +123 -0
- data/lib/utility/logger.rb +84 -0
- data/lib/utility/middleware/basic_auth.rb +27 -0
- data/lib/utility/middleware/bearer_auth.rb +27 -0
- data/lib/utility/middleware/restrict_hostnames.rb +73 -0
- data/lib/utility.rb +16 -0
- metadata +487 -0
@@ -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
|
+
|
7
|
+
# frozen_string_literal: true
|
8
|
+
|
9
|
+
require 'core/scheduler'
|
10
|
+
require 'core/connector_settings'
|
11
|
+
require 'core/elastic_connector_actions'
|
12
|
+
require 'utility/logger'
|
13
|
+
require 'utility/exception_tracking'
|
14
|
+
|
15
|
+
module Connectors
|
16
|
+
module Crawler
|
17
|
+
class Scheduler < Core::Scheduler
|
18
|
+
def connector_settings
|
19
|
+
Core::ConnectorSettings.fetch_crawler_connectors || []
|
20
|
+
rescue StandardError => e
|
21
|
+
Utility::ExceptionTracking.log_exception(e, 'Could not retrieve Crawler connectors due to unexpected error.')
|
22
|
+
[]
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def connector_registered?(service_type)
|
28
|
+
service_type == 'elastic-crawler'
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,57 @@
|
|
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/connector'
|
10
|
+
require 'utility'
|
11
|
+
|
12
|
+
module Connectors
|
13
|
+
module Example
|
14
|
+
class Connector < Connectors::Base::Connector
|
15
|
+
def self.service_type
|
16
|
+
'example'
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.display_name
|
20
|
+
'Example Connector'
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.configurable_fields
|
24
|
+
{
|
25
|
+
'foo' => {
|
26
|
+
'label' => 'Foo',
|
27
|
+
'value' => nil
|
28
|
+
}
|
29
|
+
}
|
30
|
+
end
|
31
|
+
|
32
|
+
def initialize(configuration: {})
|
33
|
+
super
|
34
|
+
end
|
35
|
+
|
36
|
+
def do_health_check
|
37
|
+
# Do the health check by trying to access 3rd-party system just to verify that everything is set up properly.
|
38
|
+
#
|
39
|
+
# To emulate unhealthy 3rd-party system situation, uncomment the following line:
|
40
|
+
# raise 'something went wrong'
|
41
|
+
end
|
42
|
+
|
43
|
+
def yield_documents
|
44
|
+
attachments = [
|
45
|
+
File.open('./lib/connectors/example/example_attachments/first_attachment.txt'),
|
46
|
+
File.open('./lib/connectors/example/example_attachments/second_attachment.txt'),
|
47
|
+
File.open('./lib/connectors/example/example_attachments/third_attachment.txt')
|
48
|
+
]
|
49
|
+
|
50
|
+
attachments.each_with_index do |att, index|
|
51
|
+
data = { id: (index + 1).to_s, name: "example document #{index + 1}", _attachment: File.read(att) }
|
52
|
+
yield data
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
e1xydGYxXGFuc2lcYW5zaWNwZzEyNTJcY29jb2FydGYyNjM5Clxjb2NvYXRleHRzY2FsaW5nMFxjb2NvYXBsYXRmb3JtMHtcZm9udHRibFxmMFxmc3dpc3NcZmNoYXJzZXQwIEhlbHZldGljYS1Cb2xkO1xmMVxmc3dpc3NcZmNoYXJzZXQwIEhlbHZldGljYTtcZjJcZnN3aXNzXGZjaGFyc2V0MCBIZWx2ZXRpY2EtT2JsaXF1ZTsKfQp7XGNvbG9ydGJsO1xyZWQyNTVcZ3JlZW4yNTVcYmx1ZTI1NTt9CntcKlxleHBhbmRlZGNvbG9ydGJsOzt9ClxwYXBlcncxMTkwMFxwYXBlcmgxNjg0MFxtYXJnbDE0NDBcbWFyZ3IxNDQwXHZpZXd3MTE1MjBcdmlld2g4NDAwXHZpZXdraW5kMApccGFyZFx0eDU2Nlx0eDExMzNcdHgxNzAwXHR4MjI2N1x0eDI4MzRcdHgzNDAxXHR4Mzk2OFx0eDQ1MzVcdHg1MTAyXHR4NTY2OVx0eDYyMzZcdHg2ODAzXHBhcmRpcm5hdHVyYWxccGFydGlnaHRlbmZhY3RvcjAKClxmMFxiXGZzMjQgXGNmMCBFeGFtcGxlIEF0dGFjaG1lbnQgMDFcClwKClxmMVxiMCBUaGlzIGlzIHRoZQpcZjJcaSAgZmlyc3QgClxmMVxpMCBvZiBcdWwgdGhyZWUgXHVsbm9uZSBleGFtcGxlIGF0dGFjaG1lbnRzfQ==
|
@@ -0,0 +1 @@
|
|
1
|
+
e1xydGYxXGFuc2lcYW5zaWNwZzEyNTJcY29jb2FydGYyNjM5Clxjb2NvYXRleHRzY2FsaW5nMFxjb2NvYXBsYXRmb3JtMHtcZm9udHRibFxmMFxmc3dpc3NcZmNoYXJzZXQwIEhlbHZldGljYS1Cb2xkO1xmMVxmc3dpc3NcZmNoYXJzZXQwIEhlbHZldGljYTtcZjJcZnN3aXNzXGZjaGFyc2V0MCBIZWx2ZXRpY2EtT2JsaXF1ZTsKfQp7XGNvbG9ydGJsO1xyZWQyNTVcZ3JlZW4yNTVcYmx1ZTI1NTt9CntcKlxleHBhbmRlZGNvbG9ydGJsOzt9ClxwYXBlcncxMTkwMFxwYXBlcmgxNjg0MFxtYXJnbDE0NDBcbWFyZ3IxNDQwXHZpZXd3MTE1MjBcdmlld2g4NDAwXHZpZXdraW5kMApccGFyZFx0eDU2Nlx0eDExMzNcdHgxNzAwXHR4MjI2N1x0eDI4MzRcdHgzNDAxXHR4Mzk2OFx0eDQ1MzVcdHg1MTAyXHR4NTY2OVx0eDYyMzZcdHg2ODAzXHBhcmRpcm5hdHVyYWxccGFydGlnaHRlbmZhY3RvcjAKClxmMFxiXGZzMjQgXGNmMCBFeGFtcGxlIEF0dGFjaG1lbnQgMDJcClwKClxmMVxiMCBUaGlzIGlzIHRoZQpcZjJcaSAgc2Vjb25kIApcZjFcaTAgb2YgXHVsIHRocmVlIFx1bG5vbmUgZXhhbXBsZSBhdHRhY2htZW50c30=
|
@@ -0,0 +1 @@
|
|
1
|
+
e1xydGYxXGFuc2lcYW5zaWNwZzEyNTJcY29jb2FydGYyNjM5Clxjb2NvYXRleHRzY2FsaW5nMFxjb2NvYXBsYXRmb3JtMHtcZm9udHRibFxmMFxmc3dpc3NcZmNoYXJzZXQwIEhlbHZldGljYS1Cb2xkO1xmMVxmc3dpc3NcZmNoYXJzZXQwIEhlbHZldGljYTtcZjJcZnN3aXNzXGZjaGFyc2V0MCBIZWx2ZXRpY2EtT2JsaXF1ZTsKfQp7XGNvbG9ydGJsO1xyZWQyNTVcZ3JlZW4yNTVcYmx1ZTI1NTt9CntcKlxleHBhbmRlZGNvbG9ydGJsOzt9ClxwYXBlcncxMTkwMFxwYXBlcmgxNjg0MFxtYXJnbDE0NDBcbWFyZ3IxNDQwXHZpZXd3MTE1MjBcdmlld2g4NDAwXHZpZXdraW5kMApccGFyZFx0eDU2Nlx0eDExMzNcdHgxNzAwXHR4MjI2N1x0eDI4MzRcdHgzNDAxXHR4Mzk2OFx0eDQ1MzVcdHg1MTAyXHR4NTY2OVx0eDYyMzZcdHg2ODAzXHBhcmRpcm5hdHVyYWxccGFydGlnaHRlbmZhY3RvcjAKClxmMFxiXGZzMjQgXGNmMCBFeGFtcGxlIEF0dGFjaG1lbnQgMDNcClwKClxmMVxiMCBUaGlzIGlzIHRoZQpcZjJcaSAgdGhpcmQgClxmMVxpMCBvZiBcdWwgdGhyZWUgXHVsbm9uZSBleGFtcGxlIGF0dGFjaG1lbnRzfQ==
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
#
|
4
|
+
# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
|
5
|
+
# or more contributor license agreements. Licensed under the Elastic License;
|
6
|
+
# you may not use this file except in compliance with the Elastic License.
|
7
|
+
#
|
8
|
+
require 'hashie/mash'
|
9
|
+
require 'active_support/core_ext/hash/indifferent_access'
|
10
|
+
require 'connectors/base/adapter'
|
11
|
+
|
12
|
+
module Connectors
|
13
|
+
module GitLab
|
14
|
+
class Adapter < Connectors::Base::Adapter
|
15
|
+
# it's important to have this to generate ID converters between the GitLab ID and the
|
16
|
+
# Enterprise Search document ID. The Enterprise Search document ID will be prefixed with the service type,
|
17
|
+
# in our case - `gitlab`.
|
18
|
+
generate_id_helpers :gitlab, 'gitlab'
|
19
|
+
|
20
|
+
def self.to_es_document(type, source_doc)
|
21
|
+
source_doc = source_doc.with_indifferent_access
|
22
|
+
result = {}
|
23
|
+
case type.to_sym
|
24
|
+
when :project
|
25
|
+
result.merge!(
|
26
|
+
{
|
27
|
+
:url => source_doc[:web_url],
|
28
|
+
:body => source_doc[:description],
|
29
|
+
:title => source_doc[:name],
|
30
|
+
:created_at => source_doc[:created_at],
|
31
|
+
:last_modified_at => source_doc[:last_activity_at],
|
32
|
+
:visibility => source_doc[:visibility],
|
33
|
+
:namespace => if source_doc[:namespace].nil?
|
34
|
+
nil
|
35
|
+
else
|
36
|
+
source_doc[:namespace][:name]
|
37
|
+
end
|
38
|
+
}
|
39
|
+
)
|
40
|
+
else
|
41
|
+
# don't remap
|
42
|
+
result.merge!(source_doc)
|
43
|
+
end
|
44
|
+
result[:id] = gitlab_id_to_es_id(source_doc[:id])
|
45
|
+
result[:type] = type
|
46
|
+
result
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,67 @@
|
|
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
|
+
require 'active_support/core_ext/hash/indifferent_access'
|
9
|
+
|
10
|
+
require 'connectors/base/connector'
|
11
|
+
require 'connectors/gitlab/extractor'
|
12
|
+
require 'connectors/gitlab/custom_client'
|
13
|
+
require 'connectors/gitlab/adapter'
|
14
|
+
require 'core/output_sink'
|
15
|
+
|
16
|
+
module Connectors
|
17
|
+
module GitLab
|
18
|
+
class Connector < Connectors::Base::Connector
|
19
|
+
def self.service_type
|
20
|
+
'gitlab'
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.display_name
|
24
|
+
'GitLab Connector'
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.configurable_fields
|
28
|
+
{
|
29
|
+
:base_url => {
|
30
|
+
:label => 'Base URL',
|
31
|
+
:value => Connectors::GitLab::DEFAULT_BASE_URL
|
32
|
+
},
|
33
|
+
:api_key => {
|
34
|
+
:label => 'API Key'
|
35
|
+
}
|
36
|
+
}
|
37
|
+
end
|
38
|
+
|
39
|
+
def initialize(configuration: {})
|
40
|
+
super
|
41
|
+
|
42
|
+
@extractor = Connectors::GitLab::Extractor.new(
|
43
|
+
:base_url => configuration.dig(:base_url, :value),
|
44
|
+
:api_token => configuration.dig(:api_token, :value)
|
45
|
+
)
|
46
|
+
end
|
47
|
+
|
48
|
+
def yield_documents
|
49
|
+
next_page_link = nil
|
50
|
+
loop do
|
51
|
+
next_page_link = @extractor.yield_projects_page(next_page_link) do |projects_chunk|
|
52
|
+
projects_chunk.each do |project|
|
53
|
+
yield Connectors::GitLab::Adapter.to_es_document(:project, project)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
break unless next_page_link.present?
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
private
|
61
|
+
|
62
|
+
def do_health_check
|
63
|
+
@extractor.health_check
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,44 @@
|
|
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
|
+
require 'faraday_middleware/response/follow_redirects'
|
7
|
+
require 'connectors/base/custom_client'
|
8
|
+
require 'utility/middleware/bearer_auth'
|
9
|
+
require 'utility/middleware/basic_auth'
|
10
|
+
require 'utility/middleware/restrict_hostnames'
|
11
|
+
|
12
|
+
require 'app/config'
|
13
|
+
|
14
|
+
module Connectors
|
15
|
+
module GitLab
|
16
|
+
DEFAULT_BASE_URL = 'https://gitlab.com/api/v4'
|
17
|
+
|
18
|
+
class CustomClient < Connectors::Base::CustomClient
|
19
|
+
attr_reader :api_token
|
20
|
+
|
21
|
+
class ClientError < StandardError
|
22
|
+
attr_reader :status_code, :endpoint, :api_token
|
23
|
+
|
24
|
+
def initialize(status_code, endpoint)
|
25
|
+
@status_code = status_code
|
26
|
+
@endpoint = endpoint
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def initialize(base_url:, api_token:, ensure_fresh_auth: nil)
|
31
|
+
@api_token = api_token
|
32
|
+
super(:base_url => base_url || DEFAULT_BASE_URL, :ensure_fresh_auth => ensure_fresh_auth)
|
33
|
+
end
|
34
|
+
|
35
|
+
def additional_middleware
|
36
|
+
[
|
37
|
+
::FaradayMiddleware::FollowRedirects,
|
38
|
+
[Utility::Middleware::RestrictHostnames, { :allowed_hosts => [base_url, DEFAULT_BASE_URL] }],
|
39
|
+
[Utility::Middleware::BearerAuth, { :bearer_auth_token => api_token }]
|
40
|
+
]
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,69 @@
|
|
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 'hashie'
|
10
|
+
require 'json'
|
11
|
+
require 'rack/utils'
|
12
|
+
require 'active_support/core_ext/hash/indifferent_access'
|
13
|
+
require 'connectors/gitlab/custom_client'
|
14
|
+
|
15
|
+
module Connectors
|
16
|
+
module GitLab
|
17
|
+
class Extractor
|
18
|
+
PAGE_SIZE = 100 # max is 100
|
19
|
+
|
20
|
+
def initialize(base_url: nil, api_token: nil, owned_only: true)
|
21
|
+
super()
|
22
|
+
@base_url = base_url
|
23
|
+
@api_token = api_token
|
24
|
+
# only get projects that user owns
|
25
|
+
@owned_only = owned_only
|
26
|
+
end
|
27
|
+
|
28
|
+
def yield_projects_page(next_page_link = nil)
|
29
|
+
query_params = {
|
30
|
+
:pagination => :keyset,
|
31
|
+
:per_page => PAGE_SIZE,
|
32
|
+
:order_by => :id,
|
33
|
+
:sort => :desc,
|
34
|
+
:owned => @owned_only
|
35
|
+
}
|
36
|
+
|
37
|
+
if next_page_link.present?
|
38
|
+
if (matcher = /(https?:[^>]*)/.match(next_page_link))
|
39
|
+
clean_query = URI.parse(matcher.captures[0]).query
|
40
|
+
query_params = Rack::Utils.parse_query(clean_query)
|
41
|
+
else
|
42
|
+
raise "Next page link has unexpected format: #{next_page_link}"
|
43
|
+
end
|
44
|
+
end
|
45
|
+
response = client.get('projects', query_params)
|
46
|
+
|
47
|
+
projects_chunk = JSON.parse(response.body)
|
48
|
+
yield projects_chunk
|
49
|
+
|
50
|
+
# return next link
|
51
|
+
response.headers['Link'] || nil
|
52
|
+
end
|
53
|
+
|
54
|
+
def health_check
|
55
|
+
# let's do a simple call to get the current user
|
56
|
+
response = client.get('user')
|
57
|
+
unless response.present? && response.status == 200
|
58
|
+
raise "Health check failed with response status #{response.status} and body #{response.body}"
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
private
|
63
|
+
|
64
|
+
def client
|
65
|
+
@client ||= Connectors::GitLab::CustomClient.new(base_url: @base_url, api_token: @api_token)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,138 @@
|
|
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/base/connector'
|
11
|
+
require 'mongo'
|
12
|
+
|
13
|
+
module Connectors
|
14
|
+
module MongoDB
|
15
|
+
class Connector < Connectors::Base::Connector
|
16
|
+
def self.service_type
|
17
|
+
'mongodb'
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.display_name
|
21
|
+
'MongoDB'
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.configurable_fields
|
25
|
+
{
|
26
|
+
:host => {
|
27
|
+
:label => 'Server Hostname'
|
28
|
+
},
|
29
|
+
:user => {
|
30
|
+
:label => 'Username'
|
31
|
+
},
|
32
|
+
:password => {
|
33
|
+
:label => 'Password'
|
34
|
+
},
|
35
|
+
:database => {
|
36
|
+
:label => 'Database'
|
37
|
+
},
|
38
|
+
:collection => {
|
39
|
+
:label => 'Collection'
|
40
|
+
},
|
41
|
+
:direct_connection => {
|
42
|
+
:label => 'Direct connection? (true/false)'
|
43
|
+
}
|
44
|
+
}
|
45
|
+
end
|
46
|
+
|
47
|
+
def initialize(configuration: {})
|
48
|
+
super
|
49
|
+
|
50
|
+
@host = configuration.dig(:host, :value)
|
51
|
+
@database = configuration.dig(:database, :value)
|
52
|
+
@collection = configuration.dig(:collection, :value)
|
53
|
+
@user = configuration.dig(:user, :value)
|
54
|
+
@password = configuration.dig(:password, :value)
|
55
|
+
@direct_connection = configuration.dig(:direct_connection, :value)
|
56
|
+
end
|
57
|
+
|
58
|
+
def yield_documents
|
59
|
+
with_client do |client|
|
60
|
+
client[@collection].find.each do |document|
|
61
|
+
doc = document.with_indifferent_access
|
62
|
+
|
63
|
+
yield serialize(doc)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
private
|
69
|
+
|
70
|
+
def do_health_check
|
71
|
+
with_client do |_client|
|
72
|
+
Utility::Logger.debug("Mongo at #{@host}/#{@database} looks healthy.")
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
def with_client
|
77
|
+
raise "Invalid value for 'Direct connection' : #{@direct_connection}." unless %w[true false].include?(@direct_connection.to_s.strip.downcase)
|
78
|
+
|
79
|
+
client = if @user.present? || @password.present?
|
80
|
+
Mongo::Client.new(
|
81
|
+
@host,
|
82
|
+
database: @database,
|
83
|
+
direct_connection: to_boolean(@direct_connection),
|
84
|
+
user: @user,
|
85
|
+
password: @password
|
86
|
+
)
|
87
|
+
else
|
88
|
+
Mongo::Client.new(
|
89
|
+
@host,
|
90
|
+
database: @database,
|
91
|
+
direct_connection: to_boolean(@direct_connection)
|
92
|
+
)
|
93
|
+
end
|
94
|
+
|
95
|
+
begin
|
96
|
+
Utility::Logger.debug("Existing Databases #{client.database_names}")
|
97
|
+
Utility::Logger.debug('Existing Collections:')
|
98
|
+
|
99
|
+
client.collections.each { |coll| Utility::Logger.debug(coll.name) }
|
100
|
+
|
101
|
+
yield client
|
102
|
+
ensure
|
103
|
+
client.close
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
def serialize(mongodb_document)
|
108
|
+
# This is some lazy serialization here.
|
109
|
+
# Problem: MongoDB has its own format of things - e.g. ids are Bson::ObjectId, which when serialized to JSON
|
110
|
+
# will produce something like: 'id': { '$oid': '536268a06d2d7019ba000000' }, which is not good for us
|
111
|
+
case mongodb_document
|
112
|
+
when BSON::ObjectId
|
113
|
+
mongodb_document.to_s
|
114
|
+
when BSON::Decimal128
|
115
|
+
mongodb_document.to_big_decimal # potential problems with NaNs but also will get treated as a string by Elasticsearch anyway
|
116
|
+
when String
|
117
|
+
# it's here cause Strings are Arrays too :/
|
118
|
+
mongodb_document.to_s
|
119
|
+
when Array
|
120
|
+
mongodb_document.map { |v| serialize(v) }
|
121
|
+
when Hash
|
122
|
+
mongodb_document.map do |key, value|
|
123
|
+
remapped_key = key == '_id' ? 'id' : key
|
124
|
+
|
125
|
+
remapped_value = serialize(value)
|
126
|
+
[remapped_key, remapped_value]
|
127
|
+
end.to_h
|
128
|
+
else
|
129
|
+
mongodb_document
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
def to_boolean(value)
|
134
|
+
value == true || value =~ (/(true|t|yes|y|1)$/i)
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
138
|
+
end
|
@@ -0,0 +1,52 @@
|
|
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
|
+
module Connectors
|
8
|
+
class Factory
|
9
|
+
attr_reader :connectors
|
10
|
+
|
11
|
+
def initialize
|
12
|
+
@connectors = {}
|
13
|
+
end
|
14
|
+
|
15
|
+
def register(name, klass)
|
16
|
+
@connectors[name] = klass
|
17
|
+
end
|
18
|
+
|
19
|
+
def registered?(name)
|
20
|
+
@connectors.has_key?(name)
|
21
|
+
end
|
22
|
+
|
23
|
+
def connector_class(name)
|
24
|
+
@connectors[name]
|
25
|
+
end
|
26
|
+
|
27
|
+
def connector(name, configuration)
|
28
|
+
klass = connector_class(name)
|
29
|
+
if klass.present?
|
30
|
+
return klass.new(configuration: configuration)
|
31
|
+
end
|
32
|
+
raise "Connector #{name} is not yet registered. You need to register it before use"
|
33
|
+
end
|
34
|
+
|
35
|
+
def registered_connectors
|
36
|
+
@connectors.keys.sort
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
REGISTRY = Factory.new
|
41
|
+
|
42
|
+
require_relative './example/connector'
|
43
|
+
REGISTRY.register(Connectors::Example::Connector.service_type, Connectors::Example::Connector)
|
44
|
+
|
45
|
+
# loading plugins (might replace this with a directory scan and conventions on names)
|
46
|
+
require_relative './gitlab/connector'
|
47
|
+
|
48
|
+
REGISTRY.register(Connectors::GitLab::Connector.service_type, Connectors::GitLab::Connector)
|
49
|
+
|
50
|
+
require_relative 'mongodb/connector'
|
51
|
+
REGISTRY.register(Connectors::MongoDB::Connector.service_type, Connectors::MongoDB::Connector)
|
52
|
+
end
|
@@ -0,0 +1,21 @@
|
|
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
|
+
module Connectors
|
10
|
+
class SyncStatus
|
11
|
+
COMPLETED = 'completed'
|
12
|
+
IN_PROGRESS = 'in_progress'
|
13
|
+
FAILED = 'failed'
|
14
|
+
|
15
|
+
STATUSES = [
|
16
|
+
COMPLETED,
|
17
|
+
IN_PROGRESS,
|
18
|
+
FAILED
|
19
|
+
]
|
20
|
+
end
|
21
|
+
end
|
data/lib/connectors.rb
ADDED
@@ -0,0 +1,16 @@
|
|
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
|
+
require 'utility'
|
8
|
+
|
9
|
+
def required_path(absolute_path)
|
10
|
+
absolute_dir = File.dirname(absolute_path)
|
11
|
+
relative_dir = absolute_dir.sub(/.*lib\/connectors/, 'connectors')
|
12
|
+
name = File.basename(absolute_path, '.rb')
|
13
|
+
File.join(relative_dir, name)
|
14
|
+
end
|
15
|
+
|
16
|
+
Dir[File.join(__dir__, 'connectors/**/*.rb')].each { |f| require required_path(f) }
|
@@ -0,0 +1,13 @@
|
|
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
|
+
module ConnectorsApp
|
8
|
+
module Errors
|
9
|
+
INVALID_API_KEY = 'INVALID_API_KEY'
|
10
|
+
UNSUPPORTED_AUTH_SCHEME = 'UNSUPPORTED_AUTH_SCHEME'
|
11
|
+
INTERNAL_SERVER_ERROR = 'INTERNAL_SERVER_ERROR'
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,24 @@
|
|
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 'app/config'
|
10
|
+
require 'app/dispatcher'
|
11
|
+
require 'app/preflight_check'
|
12
|
+
require 'utility'
|
13
|
+
|
14
|
+
class ConnectorsService
|
15
|
+
def self.run!
|
16
|
+
Utility::Environment.set_execution_environment(App::Config) do
|
17
|
+
App::PreflightCheck.run!
|
18
|
+
App::Dispatcher.start!
|
19
|
+
rescue App::PreflightCheck::CheckFailure => e
|
20
|
+
Utility::Logger.error("Preflight check failed: #{e.message}")
|
21
|
+
exit(-1)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,16 @@
|
|
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_relative 'utility'
|
10
|
+
|
11
|
+
require_relative 'connectors/connector_status'
|
12
|
+
require_relative 'connectors/sync_status'
|
13
|
+
require_relative 'core/scheduler'
|
14
|
+
require_relative 'core/elastic_connector_actions'
|
15
|
+
|
16
|
+
require_relative 'connectors/crawler/scheduler'
|
@@ -0,0 +1,48 @@
|
|
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/connector_status'
|
10
|
+
require 'connectors/registry'
|
11
|
+
require 'core/connector_settings'
|
12
|
+
require 'core/elastic_connector_actions'
|
13
|
+
require 'utility/logger'
|
14
|
+
|
15
|
+
module Core
|
16
|
+
class Configuration
|
17
|
+
class << self
|
18
|
+
|
19
|
+
def update(connector_settings, service_type = nil)
|
20
|
+
if connector_settings.connector_status == Connectors::ConnectorStatus::CREATED
|
21
|
+
connector_class = Connectors::REGISTRY.connector_class(connector_settings.service_type || service_type)
|
22
|
+
unless connector_class
|
23
|
+
Utility::Logger.error("Couldn't find connector for service type #{connector_settings.service_type || service_type}")
|
24
|
+
return
|
25
|
+
end
|
26
|
+
configuration = connector_class.configurable_fields
|
27
|
+
doc = {
|
28
|
+
:configuration => configuration
|
29
|
+
}
|
30
|
+
|
31
|
+
doc[:service_type] = service_type if service_type && connector_settings.needs_service_type?
|
32
|
+
|
33
|
+
# We want to set connector to CONFIGURED status if all configurable fields have default values
|
34
|
+
new_connector_status = if configuration.values.all? { |setting| setting[:value].present? }
|
35
|
+
Utility::Logger.debug("All connector configurable fields provided default values for #{connector_settings.formatted}.")
|
36
|
+
Connectors::ConnectorStatus::CONFIGURED
|
37
|
+
else
|
38
|
+
Connectors::ConnectorStatus::NEEDS_CONFIGURATION
|
39
|
+
end
|
40
|
+
|
41
|
+
doc[:status] = new_connector_status
|
42
|
+
Utility::Logger.info("Changing connector status to #{new_connector_status} for #{connector_settings.formatted}.")
|
43
|
+
Core::ElasticConnectorActions.update_connector_fields(connector_settings.id, doc)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|