connectors_service 8.5.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|