connectors_service 8.5.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (67) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +93 -0
  3. data/NOTICE.txt +2 -0
  4. data/bin/connectors_service +4 -0
  5. data/bin/list_connectors +4 -0
  6. data/config/connectors.yml +25 -0
  7. data/lib/app/app.rb +25 -0
  8. data/lib/app/config.rb +132 -0
  9. data/lib/app/console_app.rb +278 -0
  10. data/lib/app/dispatcher.rb +121 -0
  11. data/lib/app/menu.rb +104 -0
  12. data/lib/app/preflight_check.rb +134 -0
  13. data/lib/app/version.rb +10 -0
  14. data/lib/connectors/base/adapter.rb +119 -0
  15. data/lib/connectors/base/connector.rb +57 -0
  16. data/lib/connectors/base/custom_client.rb +111 -0
  17. data/lib/connectors/connector_status.rb +31 -0
  18. data/lib/connectors/crawler/scheduler.rb +32 -0
  19. data/lib/connectors/example/connector.rb +57 -0
  20. data/lib/connectors/example/example_attachments/first_attachment.txt +1 -0
  21. data/lib/connectors/example/example_attachments/second_attachment.txt +1 -0
  22. data/lib/connectors/example/example_attachments/third_attachment.txt +1 -0
  23. data/lib/connectors/gitlab/adapter.rb +50 -0
  24. data/lib/connectors/gitlab/connector.rb +67 -0
  25. data/lib/connectors/gitlab/custom_client.rb +44 -0
  26. data/lib/connectors/gitlab/extractor.rb +69 -0
  27. data/lib/connectors/mongodb/connector.rb +138 -0
  28. data/lib/connectors/registry.rb +52 -0
  29. data/lib/connectors/sync_status.rb +21 -0
  30. data/lib/connectors.rb +16 -0
  31. data/lib/connectors_app/// +13 -0
  32. data/lib/connectors_service.rb +24 -0
  33. data/lib/connectors_utility.rb +16 -0
  34. data/lib/core/configuration.rb +48 -0
  35. data/lib/core/connector_settings.rb +142 -0
  36. data/lib/core/elastic_connector_actions.rb +269 -0
  37. data/lib/core/heartbeat.rb +32 -0
  38. data/lib/core/native_scheduler.rb +24 -0
  39. data/lib/core/output_sink/base_sink.rb +33 -0
  40. data/lib/core/output_sink/combined_sink.rb +38 -0
  41. data/lib/core/output_sink/console_sink.rb +51 -0
  42. data/lib/core/output_sink/es_sink.rb +74 -0
  43. data/lib/core/output_sink.rb +13 -0
  44. data/lib/core/scheduler.rb +158 -0
  45. data/lib/core/single_scheduler.rb +29 -0
  46. data/lib/core/sync_job_runner.rb +111 -0
  47. data/lib/core.rb +16 -0
  48. data/lib/list_connectors.rb +22 -0
  49. data/lib/stubs/app_config.rb +35 -0
  50. data/lib/stubs/connectors/stats.rb +35 -0
  51. data/lib/stubs/service_type.rb +13 -0
  52. data/lib/utility/constants.rb +20 -0
  53. data/lib/utility/cron.rb +81 -0
  54. data/lib/utility/elasticsearch/index/language_data.yml +111 -0
  55. data/lib/utility/elasticsearch/index/mappings.rb +104 -0
  56. data/lib/utility/elasticsearch/index/text_analysis_settings.rb +226 -0
  57. data/lib/utility/environment.rb +33 -0
  58. data/lib/utility/errors.rb +132 -0
  59. data/lib/utility/es_client.rb +84 -0
  60. data/lib/utility/exception_tracking.rb +64 -0
  61. data/lib/utility/extension_mapping_util.rb +123 -0
  62. data/lib/utility/logger.rb +84 -0
  63. data/lib/utility/middleware/basic_auth.rb +27 -0
  64. data/lib/utility/middleware/bearer_auth.rb +27 -0
  65. data/lib/utility/middleware/restrict_hostnames.rb +73 -0
  66. data/lib/utility.rb +16 -0
  67. metadata +487 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: c395f06754b76baa88beae0fbd14d858c0eecdef41c5042e53ce14bb0296f70c
4
+ data.tar.gz: 85145ec2a3f589a0316663427b292f80a21fa0a87329895d69a3081deecaa237
5
+ SHA512:
6
+ metadata.gz: 0c5946fccce23ac02c552c5000759e280c335575236db6836bebbb6318589a02ee67df3753a8e988b286172a6b90e905cb02f22da69dc441c7430d6860bb990c
7
+ data.tar.gz: 4ee0b23db692918aa26507e141f2e0f7afc6eb70c42c717c60a3d0420cd5da161ccbda167805ecf75e88cf86c78dbec0107aa0399aa5132e692e9496ca1722cf
data/LICENSE ADDED
@@ -0,0 +1,93 @@
1
+ Elastic License 2.0
2
+
3
+ URL: https://www.elastic.co/licensing/elastic-license
4
+
5
+ ## Acceptance
6
+
7
+ By using the software, you agree to all of the terms and conditions below.
8
+
9
+ ## Copyright License
10
+
11
+ The licensor grants you a non-exclusive, royalty-free, worldwide,
12
+ non-sublicensable, non-transferable license to use, copy, distribute, make
13
+ available, and prepare derivative works of the software, in each case subject to
14
+ the limitations and conditions below.
15
+
16
+ ## Limitations
17
+
18
+ You may not provide the software to third parties as a hosted or managed
19
+ service, where the service provides users with access to any substantial set of
20
+ the features or functionality of the software.
21
+
22
+ You may not move, change, disable, or circumvent the license key functionality
23
+ in the software, and you may not remove or obscure any functionality in the
24
+ software that is protected by the license key.
25
+
26
+ You may not alter, remove, or obscure any licensing, copyright, or other notices
27
+ of the licensor in the software. Any use of the licensor’s trademarks is subject
28
+ to applicable law.
29
+
30
+ ## Patents
31
+
32
+ The licensor grants you a license, under any patent claims the licensor can
33
+ license, or becomes able to license, to make, have made, use, sell, offer for
34
+ sale, import and have imported the software, in each case subject to the
35
+ limitations and conditions in this license. This license does not cover any
36
+ patent claims that you cause to be infringed by modifications or additions to
37
+ the software. If you or your company make any written claim that the software
38
+ infringes or contributes to infringement of any patent, your patent license for
39
+ the software granted under these terms ends immediately. If your company makes
40
+ such a claim, your patent license ends immediately for work on behalf of your
41
+ company.
42
+
43
+ ## Notices
44
+
45
+ You must ensure that anyone who gets a copy of any part of the software from you
46
+ also gets a copy of these terms.
47
+
48
+ If you modify the software, you must include in any modified copies of the
49
+ software prominent notices stating that you have modified the software.
50
+
51
+ ## No Other Rights
52
+
53
+ These terms do not imply any licenses other than those expressly granted in
54
+ these terms.
55
+
56
+ ## Termination
57
+
58
+ If you use the software in violation of these terms, such use is not licensed,
59
+ and your licenses will automatically terminate. If the licensor provides you
60
+ with a notice of your violation, and you cease all violation of this license no
61
+ later than 30 days after you receive that notice, your licenses will be
62
+ reinstated retroactively. However, if you violate these terms after such
63
+ reinstatement, any additional violation of these terms will cause your licenses
64
+ to terminate automatically and permanently.
65
+
66
+ ## No Liability
67
+
68
+ *As far as the law allows, the software comes as is, without any warranty or
69
+ condition, and the licensor will not be liable to you for any damages arising
70
+ out of these terms or the use or nature of the software, under any kind of
71
+ legal claim.*
72
+
73
+ ## Definitions
74
+
75
+ The **licensor** is the entity offering these terms, and the **software** is the
76
+ software the licensor makes available under these terms, including any portion
77
+ of it.
78
+
79
+ **you** refers to the individual or entity agreeing to these terms.
80
+
81
+ **your company** is any legal entity, sole proprietorship, or other kind of
82
+ organization that you work for, plus all organizations that have control over,
83
+ are under the control of, or are under common control with that
84
+ organization. **control** means ownership of substantially all the assets of an
85
+ entity, or the power to direct its management and policies by vote, contract, or
86
+ otherwise. Control can be direct or indirect.
87
+
88
+ **your licenses** are all the licenses granted to you for the software under
89
+ these terms.
90
+
91
+ **use** means anything you do with the software requiring one of your licenses.
92
+
93
+ **trademark** means trademarks, service marks, and similar rights.
data/NOTICE.txt ADDED
@@ -0,0 +1,2 @@
1
+ connectors
2
+ Copyright 2022 Elasticsearch B.V.
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'connectors_service'
4
+ ConnectorsService.run!
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'list_connectors'
4
+ ListConnectors.run!
@@ -0,0 +1,25 @@
1
+ # general metadata
2
+ version: 8.5.0.1
3
+ repository: https://github.com/elastic/connectors-ruby.git
4
+ revision: 5392882cd3d8c03eafc68b2b1bf3c42c92ff3559
5
+ elasticsearch:
6
+ cloud_id: CHANGEME
7
+ hosts: http://localhost:9200
8
+ api_key: CHANGEME
9
+ retry_on_failure: 3
10
+ request_timeout: 120
11
+ disable_warnings: true
12
+ trace: false
13
+ log: false
14
+ thread_pool:
15
+ min_threads: 0
16
+ max_threads: 5
17
+ max_queue: 100
18
+ log_level: info
19
+ ecs_logging: true
20
+ poll_interval: 3
21
+ termination_timeout: 60
22
+ heartbeat_interval: 1800
23
+ native_mode: true
24
+ connector_id: CHANGEME
25
+ service_type: CHANGEME
data/lib/app/app.rb ADDED
@@ -0,0 +1,25 @@
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
+ $LOAD_PATH << '../'
10
+
11
+ require 'app/dispatcher'
12
+ require 'app/config'
13
+ require 'app/preflight_check'
14
+ require 'utility/environment'
15
+ require 'utility/logger'
16
+
17
+ module App
18
+ Utility::Environment.set_execution_environment(App::Config) do
19
+ App::PreflightCheck.run!
20
+ App::Dispatcher.start!
21
+ rescue App::PreflightCheck::CheckFailure => e
22
+ Utility::Logger.error("Preflight check failed: #{e.message}")
23
+ exit(-1)
24
+ end
25
+ end
data/lib/app/config.rb ADDED
@@ -0,0 +1,132 @@
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 'config'
10
+ require_relative '../utility/logger'
11
+
12
+ # We look for places in this order:
13
+ # - CONNECTORS_CONFIG environment variable
14
+ # - here: /../../config/connectors.yml
15
+ CONFIG_FILE = ENV['CONNECTORS_CONFIG'] || File.join(__dir__, '../..', 'config', 'connectors.yml')
16
+
17
+ puts "Parsing #{CONFIG_FILE} configuration file."
18
+
19
+ ::Config.setup do |config|
20
+ config.evaluate_erb_in_yaml = false
21
+ config.use_env = true
22
+ config.env_prefix = ''
23
+
24
+ config.schema do
25
+ required(:version).value(:string)
26
+ required(:repository).value(:string)
27
+ required(:revision).value(:string)
28
+
29
+ required(:elasticsearch).hash do
30
+ optional(:cloud_id).value(:string)
31
+ optional(:hosts).value(:string)
32
+ optional(:api_key).value(:string)
33
+ optional(:retry_on_failure).value(:integer)
34
+ optional(:request_timeout).value(:integer)
35
+ optional(:disable_warnings).value(:bool?)
36
+ optional(:trace).value(:bool?)
37
+ optional(:log).value(:bool?)
38
+ end
39
+
40
+ optional(:thread_pool).hash do
41
+ optional(:min_threads).value(:integer, gteq?: 0)
42
+ optional(:max_threads).value(:integer, gteq?: 0)
43
+ optional(:max_queue).value(:integer, gteq?: 0)
44
+ end
45
+
46
+ required(:native_mode).value(:bool?)
47
+ optional(:connector_id).value(:string)
48
+ optional(:service_type).value(:string)
49
+ required(:log_level).value(:string)
50
+ required(:ecs_logging).value(:bool?)
51
+
52
+ optional(:poll_interval).value(:integer)
53
+ optional(:termination_timeout).value(:integer)
54
+ optional(:heartbeat_interval).value(:integer)
55
+ end
56
+ end
57
+
58
+ ::Config.load_and_set_settings(CONFIG_FILE)
59
+
60
+ module App
61
+ DEFAULT_PASSWORD = 'changeme'
62
+
63
+ # If it's on cloud (i.e. EnvVar ENT_SEARCH_CONFIG_PATH is set), elasticsearch config in ent-search will be used.
64
+ def self.ent_search_es_config
65
+ ent_search_config_path = ENV['ENT_SEARCH_CONFIG_PATH']
66
+ unless ent_search_config_path
67
+ Utility::Logger.info('ENT_SEARCH_CONFIG_PATH is not found, use connector service config.')
68
+ return nil
69
+ end
70
+
71
+ Utility::Logger.info("Found ENT_SEARCH_CONFIG_PATH, loading ent-search config from #{ent_search_config_path}")
72
+ ent_search_config = begin
73
+ YAML.load_file(ent_search_config_path)
74
+ rescue StandardError => e
75
+ Utility::Logger.error("Failed to load ent-search config #{ent_search_config_path}: #{e.message}")
76
+ return nil
77
+ end
78
+
79
+ unless ent_search_config.is_a?(Hash)
80
+ Utility::Logger.error("Invalid ent-search config: #{ent_search_config.inspect}")
81
+ return nil
82
+ end
83
+
84
+ host = ent_search_config['elasticsearch.host'] || ent_search_config.dig('elasticsearch', 'host')
85
+ username = ent_search_config['elasticsearch.username'] || ent_search_config.dig('elasticsearch', 'username')
86
+ password = ent_search_config['elasticsearch.password'] || ent_search_config.dig('elasticsearch', 'password')
87
+
88
+ missing_fields = []
89
+ missing_fields << 'elasticsearch.host' unless host
90
+ missing_fields << 'elasticsearch.username' unless username
91
+ missing_fields << 'elasticsearch.password' unless password
92
+ if missing_fields.any?
93
+ Utility::Logger.error("Incomplete elasticsearch config, missing #{missing_fields.join(', ')}")
94
+ return nil
95
+ end
96
+
97
+ uri = begin
98
+ URI.parse(host)
99
+ rescue URI::InvalidURIError => e
100
+ Utility::Logger.error("Failed to parse elasticsearch host #{host}: #{e.message}")
101
+ return nil
102
+ end
103
+
104
+ unless uri.is_a?(URI::HTTP) || uri.is_a?(URI::HTTPS)
105
+ Utility::Logger.error("Invalid elasticsearch host #{host}, it must be a http or https URI.")
106
+ return nil
107
+ end
108
+
109
+ {
110
+ :hosts => [
111
+ {
112
+ scheme: uri.scheme,
113
+ user: username,
114
+ password: password,
115
+ host: uri.host,
116
+ port: uri.port
117
+ }
118
+ ]
119
+ }
120
+ end
121
+
122
+ Config = ::Settings.tap do |config|
123
+ if ent_search_config = ent_search_es_config
124
+ Utility::Logger.error('Overriding elasticsearch config with ent-search config')
125
+ original_es_config = config[:elasticsearch].to_h
126
+ original_es_config.delete(:cloud_id)
127
+ original_es_config.delete(:hosts)
128
+ original_es_config.delete(:api_key)
129
+ config[:elasticsearch] = original_es_config.merge(ent_search_config)
130
+ end
131
+ end
132
+ end
@@ -0,0 +1,278 @@
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
+ $LOAD_PATH << '../'
10
+
11
+ require 'app/config'
12
+ require 'app/menu'
13
+ require 'app/preflight_check'
14
+ require 'app/worker'
15
+ require 'connectors/registry'
16
+ require 'utility'
17
+ require 'core'
18
+
19
+ module App
20
+ module ConsoleApp
21
+ extend self
22
+
23
+ INDEX_NAME_REGEXP = /[a-zA-Z]+[\d_\-a-zA-Z]*/
24
+
25
+ @commands = [
26
+ { :command => :sync_now, :hint => 'start one-time synchronization NOW' },
27
+ { :command => :register, :hint => 'register connector with Elasticsearch' },
28
+ { :command => :scheduling_on, :hint => 'enable connector scheduling' },
29
+ { :command => :scheduling_off, :hint => 'disable connector scheduling' },
30
+ { :command => :set_configurable_field, :hint => 'update the values of configurable fields' },
31
+ { :command => :read_configurable_fields, :hint => 'read the stored values of configurable fields' },
32
+ { :command => :status, :hint => 'check the status of a third-party service' },
33
+ { :command => :exit, :hint => 'end the program' }
34
+ ]
35
+
36
+ def connector_id
37
+ App::Config[:connector_id]
38
+ end
39
+
40
+ def update_connector_id(connector_id)
41
+ App::Config[:connector_id] = connector_id
42
+ end
43
+
44
+ def start_sync_now
45
+ return unless connector_registered?
46
+ puts 'Initiating synchronization NOW...'
47
+ Core::ElasticConnectorActions.force_sync(connector_id)
48
+ puts "Successfully synced for connector #{connector_id}"
49
+
50
+ config_settings = Core::ConnectorSettings.fetch_by_id(connector_id)
51
+ Core::ElasticConnectorActions.ensure_content_index_exists(config_settings[:index_name])
52
+ Core::SyncJobRunner.new(config_settings, App::Config[:service_type]).execute
53
+ end
54
+
55
+ def show_status
56
+ return unless connector_registered?
57
+ connector = current_connector
58
+
59
+ puts 'Checking status...'
60
+ puts connector.source_status
61
+ puts
62
+ end
63
+
64
+ def register_connector
65
+ if connector_id.present?
66
+ puts "You already have registered a connector with ID: #{connector_id}. Registering a new connector will overwrite the existing one."
67
+ puts 'Are you sure you want to continue? (y/N)'
68
+ return false unless gets.chomp.strip.casecmp('y').zero?
69
+ end
70
+ puts 'Please enter index name for data ingestion. Use only letters, underscored and dashes.'
71
+ index_name = gets.chomp.strip
72
+ unless INDEX_NAME_REGEXP.match?(index_name)
73
+ puts "Index name #{index_name} contains symbols that aren't allowed!"
74
+ return false
75
+ end
76
+ puts 'Do you want to use ICU Analysis Plugin? (y/N)'
77
+ use_analysis_icu = gets.chomp.strip.casecmp('y').zero?
78
+ language_code = select_analyzer
79
+ # create the connector
80
+ created_id = create_connector(index_name, use_analysis_icu, language_code)
81
+ update_connector_id(created_id)
82
+ true
83
+ end
84
+
85
+ def validate_cronline(cronline)
86
+ !!Fugit::Cron.parse(Utility::Cron.quartz_to_crontab(cronline))
87
+ end
88
+
89
+ def enable_scheduling
90
+ return unless connector_registered?
91
+
92
+ previous_schedule = Core::ConnectorSettings.fetch_by_id(connector_id)&.scheduling_settings&.fetch(:interval, nil)
93
+ if previous_schedule.present?
94
+ puts "Please enter a valid crontab expression for scheduling. Previous schedule was: #{previous_schedule}."
95
+ else
96
+ puts 'Please enter a valid crontab expression for scheduling.'
97
+ end
98
+ cron_expression = gets.chomp.strip.downcase
99
+ unless validate_cronline(cron_expression)
100
+ puts "Quartz Cron expression #{cron_expression} isn't valid!"
101
+ return
102
+ end
103
+ Core::ElasticConnectorActions.enable_connector_scheduling(connector_id, cron_expression)
104
+ puts "Enabled scheduling for connector #{connector_id} with cron expression #{cron_expression}"
105
+ end
106
+
107
+ def disable_scheduling
108
+ return unless connector_registered?
109
+ puts "Are you sure you want to disable scheduling for connector #{connector_id}? (y/n)"
110
+ return unless gets.chomp.strip.casecmp('y').zero?
111
+ Core::ElasticConnectorActions.disable_connector_scheduling(connector_id)
112
+ puts "Disabled scheduling for connector #{connector_id}"
113
+ end
114
+
115
+ def connector_registered?(warn_if_not: true)
116
+ result = connector_id.present?
117
+ if warn_if_not && !result
118
+ 'You have no connector ID yet. Register a new connector before continuing.'
119
+ end
120
+ result
121
+ end
122
+
123
+ def create_connector(index_name, use_analysis_icu = false, language_code = :en)
124
+ id = Core::ElasticConnectorActions.create_connector(index_name, App::Config[:service_type])
125
+ Core::ElasticConnectorActions.ensure_content_index_exists(index_name, use_analysis_icu, language_code)
126
+ puts "Successfully registered connector #{index_name} with ID #{id}"
127
+
128
+ connector_settings = Core::ConnectorSettings.fetch_by_id(id)
129
+
130
+ connector_settings.id
131
+ end
132
+
133
+ def read_command
134
+ menu = App::Menu.new('Please select the command:', @commands)
135
+ menu.select_command
136
+ rescue Interrupt
137
+ exit_normally
138
+ end
139
+
140
+ def select_analyzer
141
+ analyzers = App::Menu.new('Please select a language analyzer', supported_analyzers)
142
+ analyzers.select_command
143
+ rescue Interrupt
144
+ exit_normally
145
+ end
146
+
147
+ def supported_analyzers
148
+ @supported_analyzers ||= YAML.safe_load(
149
+ File.read(Utility::Elasticsearch::Index::TextAnalysisSettings::LANGUAGE_DATA_FILE_PATH),
150
+ symbolize_names: true
151
+ ).map do |language_code, data|
152
+ { :command => language_code, :hint => data[:name] }
153
+ end
154
+ puts @supported_analyzers
155
+ @supported_analyzers
156
+ end
157
+
158
+ def wait_for_keypress(message = nil)
159
+ if message.present?
160
+ puts message
161
+ end
162
+ puts 'Press any key to continue...'
163
+ gets
164
+ end
165
+
166
+ def current_connector
167
+ connector_settings = Core::ConnectorSettings.fetch_by_id(App::Config[:connector_id])
168
+ service_type = App::Config[:service_type]
169
+ if service_type.present?
170
+ return registry.connector(service_type, connector_settings.configuration)
171
+ end
172
+ puts 'You have not set connector service type in settings. Please do so before continuing.'
173
+ nil
174
+ end
175
+
176
+ def exit_normally(message = 'Kthxbye!... ¯\_(ツ)_/¯')
177
+ puts(message)
178
+ exit(true)
179
+ end
180
+
181
+ def registry
182
+ @registry = Connectors::REGISTRY
183
+ end
184
+
185
+ puts 'Hello Connectors 3.0!'
186
+ sleep(1)
187
+
188
+ def set_configurable_field
189
+ return unless connector_registered?
190
+
191
+ connector = current_connector
192
+ connector_class = connector.class
193
+ current_values = Core::ConnectorSettings.fetch_by_id(connector_id)&.configuration
194
+ return unless connector.present?
195
+
196
+ puts 'Provided configurable fields:'
197
+ configurable_fields = connector_class.configurable_fields
198
+ fields = configurable_fields.each_key.map do |key|
199
+ field = configurable_fields[key].with_indifferent_access
200
+ current_value = current_values&.fetch(key, nil)
201
+ { :command => key, :hint => "#{field[:label]} (current value: #{current_value}, default: #{field[:value]})" }
202
+ end
203
+
204
+ menu = App::Menu.new('Please select the configurable field:', fields)
205
+ field_name = menu.select_command
206
+ field_label = configurable_fields.dig(field_name, :label)
207
+
208
+ puts 'Please enter the new value:'
209
+ new_value = gets.chomp.strip
210
+ Core::ElasticConnectorActions.set_configurable_field(connector_id, field_name, field_label, new_value)
211
+ Utility::Logger.debug("Successfully updated field #{field_name} for connector #{connector_id} to #{new_value}")
212
+ end
213
+
214
+ def read_configurable_fields
215
+ return unless connector_registered?
216
+
217
+ connector = current_connector
218
+ connector_class = connector.class
219
+
220
+ current_values = Core::ConnectorSettings.fetch_by_id(connector_id)&.configuration
221
+ return unless connector.present?
222
+
223
+ puts 'Persisted values of configurable fields:'
224
+ connector_class.configurable_fields.each_key.each do |key|
225
+ field = connector_class.configurable_fields[key].with_indifferent_access
226
+ current_value = current_values&.fetch(key, nil)
227
+ puts "* #{field[:label]} - current value: #{current_value}, default: #{field[:value]}"
228
+ end
229
+ end
230
+
231
+ Utility::Environment.set_execution_environment(App::Config) do
232
+ App::PreflightCheck.run!
233
+ loop do
234
+ command = read_command
235
+ case command
236
+ when :sync_now
237
+ start_sync_now
238
+ wait_for_keypress('Synchronization finished!')
239
+ when :status
240
+ show_status
241
+ wait_for_keypress('Status checked!')
242
+ when :register
243
+ if register_connector
244
+ wait_for_keypress('Please store connector ID in config file and restart the program.')
245
+ else
246
+ wait_for_keypress('Registration canceled!')
247
+ end
248
+ when :scheduling_on
249
+ enable_scheduling
250
+ wait_for_keypress('Scheduling enabled! Start synchronization to see it in action.')
251
+ when :scheduling_off
252
+ disable_scheduling
253
+ wait_for_keypress('Scheduling disabled! Starting synchronization will have no effect now.')
254
+ when :set_configurable_field
255
+ set_configurable_field
256
+ wait_for_keypress('Configurable field is updated!')
257
+ when :read_configurable_fields
258
+ read_configurable_fields
259
+ wait_for_keypress
260
+ when :exit
261
+ exit_normally
262
+ else
263
+ exit_normally('Sorry, this command is not yet implemented')
264
+ end
265
+ end
266
+ end
267
+ rescue App::PreflightCheck::CheckFailure => e
268
+ Utility::Logger.error("Preflight check failed: #{e.message}")
269
+ exit(-1)
270
+ rescue SystemExit
271
+ puts 'Exiting.'
272
+ rescue Interrupt
273
+ exit_normally
274
+ rescue StandardError => e
275
+ Utility::ExceptionTracking.log_exception(e)
276
+ exit(false)
277
+ end
278
+ end