manageiq-appliance_console 1.0.0

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.
Files changed (50) hide show
  1. checksums.yaml +7 -0
  2. data/.codeclimate.yml +47 -0
  3. data/.gitignore +12 -0
  4. data/.rspec +4 -0
  5. data/.rspec_ci +4 -0
  6. data/.rubocop.yml +4 -0
  7. data/.rubocop_cc.yml +5 -0
  8. data/.rubocop_local.yml +2 -0
  9. data/.travis.yml +19 -0
  10. data/Gemfile +6 -0
  11. data/LICENSE.txt +202 -0
  12. data/README.md +45 -0
  13. data/Rakefile +6 -0
  14. data/bin/appliance_console +661 -0
  15. data/bin/appliance_console_cli +7 -0
  16. data/lib/manageiq-appliance_console.rb +51 -0
  17. data/lib/manageiq/appliance_console/certificate.rb +146 -0
  18. data/lib/manageiq/appliance_console/certificate_authority.rb +140 -0
  19. data/lib/manageiq/appliance_console/cli.rb +363 -0
  20. data/lib/manageiq/appliance_console/database_configuration.rb +286 -0
  21. data/lib/manageiq/appliance_console/database_maintenance.rb +35 -0
  22. data/lib/manageiq/appliance_console/database_maintenance_hourly.rb +58 -0
  23. data/lib/manageiq/appliance_console/database_maintenance_periodic.rb +84 -0
  24. data/lib/manageiq/appliance_console/database_replication.rb +146 -0
  25. data/lib/manageiq/appliance_console/database_replication_primary.rb +59 -0
  26. data/lib/manageiq/appliance_console/database_replication_standby.rb +166 -0
  27. data/lib/manageiq/appliance_console/date_time_configuration.rb +117 -0
  28. data/lib/manageiq/appliance_console/errors.rb +5 -0
  29. data/lib/manageiq/appliance_console/external_auth_options.rb +153 -0
  30. data/lib/manageiq/appliance_console/external_database_configuration.rb +34 -0
  31. data/lib/manageiq/appliance_console/external_httpd_authentication.rb +157 -0
  32. data/lib/manageiq/appliance_console/external_httpd_authentication/external_httpd_configuration.rb +249 -0
  33. data/lib/manageiq/appliance_console/internal_database_configuration.rb +187 -0
  34. data/lib/manageiq/appliance_console/key_configuration.rb +118 -0
  35. data/lib/manageiq/appliance_console/logfile_configuration.rb +117 -0
  36. data/lib/manageiq/appliance_console/logger.rb +23 -0
  37. data/lib/manageiq/appliance_console/logging.rb +102 -0
  38. data/lib/manageiq/appliance_console/logical_volume_management.rb +94 -0
  39. data/lib/manageiq/appliance_console/principal.rb +46 -0
  40. data/lib/manageiq/appliance_console/prompts.rb +211 -0
  41. data/lib/manageiq/appliance_console/scap.rb +53 -0
  42. data/lib/manageiq/appliance_console/temp_storage_configuration.rb +79 -0
  43. data/lib/manageiq/appliance_console/timezone_configuration.rb +58 -0
  44. data/lib/manageiq/appliance_console/utilities.rb +67 -0
  45. data/lib/manageiq/appliance_console/version.rb +5 -0
  46. data/locales/appliance/en.yml +42 -0
  47. data/locales/container/en.yml +30 -0
  48. data/manageiq-appliance_console.gemspec +40 -0
  49. data/zanata.xml +7 -0
  50. metadata +317 -0
@@ -0,0 +1,286 @@
1
+ require 'active_record'
2
+ require 'active_support/core_ext'
3
+ require 'linux_admin'
4
+ require 'pathname'
5
+ require 'util/miq-password'
6
+ require 'fileutils'
7
+
8
+ module ManageIQ
9
+ module ApplianceConsole
10
+ class DatabaseConfiguration
11
+ attr_accessor :adapter, :host, :username, :database, :password, :port, :region
12
+
13
+ class ModelWithNoBackingTable < ActiveRecord::Base
14
+ end
15
+
16
+ DB_YML = ManageIQ::ApplianceConsole::RAILS_ROOT.join("config/database.yml")
17
+ DB_YML_TMPL = ManageIQ::ApplianceConsole::RAILS_ROOT.join("config/database.pg.yml")
18
+
19
+ CREATE_REGION_AGREE = "WARNING: Creating a database region will destroy any existing data and cannot be undone.\n\nAre you sure you want to continue? (Y/N):".freeze
20
+ FAILED_WITH_ERROR_HYPHEN = "failed with error -".freeze
21
+
22
+ # PG 9.2 bigint max 9223372036854775807 / ArRegion::DEFAULT_RAILS_SEQUENCE_FACTOR = 9223372
23
+ # http://www.postgresql.org/docs/9.2/static/datatype-numeric.html
24
+ # 9223372 won't be a full region though, so we're not including it.
25
+ # TODO: This information should be shared outside of appliance console code and MiqRegion.
26
+ REGION_RANGE = 0..9223371
27
+ DEFAULT_PORT = 5432
28
+
29
+ include ManageIQ::ApplianceConsole::Logging
30
+
31
+ def initialize(hash = {})
32
+ initialize_from_hash(hash)
33
+ @adapter ||= "postgresql"
34
+ # introduced by Logging
35
+ self.interactive = true unless hash.key?(:interactive)
36
+ end
37
+
38
+ def run_interactive
39
+ ask_questions
40
+
41
+ clear_screen
42
+ say "Activating the configuration using the following settings...\n#{friendly_inspect}\n"
43
+
44
+ raise MiqSignalError unless activate
45
+
46
+ post_activation
47
+ say("\nConfiguration activated successfully.\n")
48
+ rescue RuntimeError => e
49
+ puts "Configuration failed#{": " + e.message unless e.class == MiqSignalError}"
50
+ press_any_key
51
+ raise MiqSignalError
52
+ end
53
+
54
+ def local?
55
+ host.blank? || host.in?(%w(localhost 127.0.0.1))
56
+ end
57
+
58
+ def password=(value)
59
+ @password = MiqPassword.try_decrypt(value)
60
+ end
61
+
62
+ def activate
63
+ return false unless validated
64
+
65
+ original = self.class.current
66
+ success = false
67
+
68
+ begin
69
+ save
70
+ success = create_or_join_region
71
+ rescue
72
+ success = false
73
+ ensure
74
+ save(original) unless success
75
+ end
76
+ end
77
+
78
+ def create_or_join_region
79
+ region ? create_region : join_region
80
+ end
81
+
82
+ def create_region
83
+ ManageIQ::ApplianceConsole::Utilities.bail_if_db_connections("preventing the setup of a database region")
84
+ log_and_feedback(__method__) do
85
+ ManageIQ::ApplianceConsole::Utilities.rake("evm:db:region", ["--", {:region => region}])
86
+ end
87
+ end
88
+
89
+ def join_region
90
+ ManageIQ::ApplianceConsole::Utilities.rake("evm:join_region", {})
91
+ end
92
+
93
+ def reset_region
94
+ say("Warning: RESETTING A DATABASE WILL DESTROY ANY EXISTING DATA AND CANNOT BE UNDONE.\n\n")
95
+ raise MiqSignalError unless are_you_sure?("reset the configured database")
96
+
97
+ create_new_region_questions(false)
98
+ ENV["DISABLE_DATABASE_ENVIRONMENT_CHECK"] = "1"
99
+ create_region
100
+ ensure
101
+ ENV["DISABLE_DATABASE_ENVIRONMENT_CHECK"] = nil
102
+ end
103
+
104
+ def create_new_region_questions(warn = true)
105
+ clear_screen
106
+ say("\n\nNote: Creating a new database region requires an empty database.") if warn
107
+ say("Each database region number must be unique.\n")
108
+ self.region = ask_for_integer("database region number", REGION_RANGE)
109
+ raise MiqSignalError if warn && !agree(CREATE_REGION_AGREE)
110
+ end
111
+
112
+ def ask_for_database_credentials(password_twice = true)
113
+ self.host = ask_for_ip_or_hostname("database hostname or IP address", host) if host.blank? || !local?
114
+ self.port = ask_for_integer("port number", nil, port) unless local?
115
+ self.database = just_ask("name of the database on #{host}", database) unless local?
116
+ self.username = just_ask("username", username) unless local?
117
+ count = 0
118
+ loop do
119
+ password1 = ask_for_password("database password on #{host}", password)
120
+ # if they took the default, just bail
121
+ break if (password1 == password)
122
+
123
+ if password1.strip.length == 0
124
+ say("\nPassword can not be empty, please try again")
125
+ next
126
+ end
127
+ if password_twice
128
+ password2 = ask_for_password("database password again")
129
+ if password1 == password2
130
+ self.password = password1
131
+ break
132
+ elsif count > 0 # only reprompt password once
133
+ raise "passwords did not match"
134
+ else
135
+ count += 1
136
+ say("\nThe passwords did not match, please try again")
137
+ end
138
+ else
139
+ self.password = password1
140
+ break
141
+ end
142
+ end
143
+ end
144
+
145
+ def friendly_inspect
146
+ output = <<-FRIENDLY
147
+ Host: #{host}
148
+ Username: #{username}
149
+ Database: #{database}
150
+ FRIENDLY
151
+ output << "Port: #{port}\n" if port
152
+ output << "Region: #{region}\n" if region
153
+ output
154
+ end
155
+
156
+ def settings_hash
157
+ {
158
+ 'adapter' => 'postgresql',
159
+ 'host' => local? ? "localhost" : host,
160
+ 'port' => port,
161
+ 'username' => username,
162
+ 'password' => password.presence,
163
+ 'database' => database
164
+ }
165
+ end
166
+
167
+ # merge all the non specified setings
168
+ # for all the basic attributes, overwrite from this object (including blank values)
169
+ def merged_settings
170
+ merged = self.class.current
171
+ settings_hash.each do |k, v|
172
+ if v.present?
173
+ merged['production'][k] = v
174
+ else
175
+ merged['production'].delete(k)
176
+ end
177
+ end
178
+ merged
179
+ end
180
+
181
+ def save(settings = nil)
182
+ settings ||= merged_settings
183
+ settings = self.class.encrypt_password(settings)
184
+ do_save(settings)
185
+ end
186
+
187
+ def self.encrypt_password(settings)
188
+ encrypt_decrypt_password(settings) { |pass| MiqPassword.try_encrypt(pass) }
189
+ end
190
+
191
+ def self.decrypt_password(settings)
192
+ encrypt_decrypt_password(settings) { |pass| MiqPassword.try_decrypt(pass) }
193
+ end
194
+
195
+ def self.current
196
+ decrypt_password(load_current)
197
+ end
198
+
199
+ def self.database_yml_configured?
200
+ File.exist?(DB_YML) && File.exist?(KEY_FILE)
201
+ end
202
+
203
+ def self.database_host
204
+ database_yml_configured? ? current[rails_env]['host'] || "localhost" : nil
205
+ end
206
+
207
+ def self.database_name
208
+ database_yml_configured? ? current[rails_env]['database'] : nil
209
+ end
210
+
211
+ def self.region
212
+ database_yml_configured? ? ManageIQ::ApplianceConsole::Utilities.db_region : nil
213
+ end
214
+
215
+ def validated
216
+ !!validate!
217
+ rescue => err
218
+ say_error(__method__, err.message)
219
+ log_error(__method__, err.message)
220
+ false
221
+ end
222
+
223
+ def validate!
224
+ pool = ModelWithNoBackingTable.establish_connection(settings_hash.delete_if { |_n, v| v.blank? })
225
+ begin
226
+ pool.connection
227
+ ensure
228
+ ModelWithNoBackingTable.remove_connection
229
+ end
230
+ end
231
+
232
+ def start_evm
233
+ pid = fork do
234
+ begin
235
+ LinuxAdmin::Service.new("evmserverd").enable.start
236
+ rescue => e
237
+ logger.error("Failed to enable and start evmserverd service: #{e.message}")
238
+ logger.error(e.backtrace.join("\n"))
239
+ end
240
+ end
241
+ Process.detach(pid)
242
+ end
243
+
244
+ private
245
+
246
+ def self.rails_env
247
+ ENV["RAILS_ENV"] || "development"
248
+ end
249
+ private_class_method :rails_env
250
+
251
+ def self.encrypt_decrypt_password(settings)
252
+ new_settings = {}
253
+ settings.each_key { |section| new_settings[section] = settings[section].dup }
254
+ pass = new_settings["production"]["password"]
255
+ new_settings["production"]["password"] = yield(pass) if pass
256
+ new_settings
257
+ end
258
+
259
+ def self.load_current
260
+ require 'yaml'
261
+ unless File.exist?(DB_YML)
262
+ require 'fileutils'
263
+ FileUtils.cp(DB_YML_TMPL, DB_YML) if File.exist?(DB_YML_TMPL)
264
+ end
265
+ YAML.load_file(DB_YML)
266
+ end
267
+
268
+ def do_save(settings)
269
+ require 'yaml'
270
+ File.write(DB_YML, YAML.dump(settings))
271
+ end
272
+
273
+ def initialize_from_hash(hash)
274
+ hash.each do |k, v|
275
+ next if v.nil?
276
+ setter = "#{k}="
277
+ if self.respond_to?(setter)
278
+ public_send(setter, v)
279
+ else
280
+ raise ArgumentError, "Invalid argument: #{k}"
281
+ end
282
+ end
283
+ end
284
+ end
285
+ end
286
+ end
@@ -0,0 +1,35 @@
1
+ require 'fileutils'
2
+
3
+ module ManageIQ
4
+ module ApplianceConsole
5
+ class DatabaseMaintenance
6
+ include ManageIQ::ApplianceConsole::Logging
7
+
8
+ attr_accessor :hourly, :executed_hourly_action, :requested_hourly_action
9
+ attr_accessor :periodic, :executed_periodic_action, :requested_periodic_action
10
+
11
+ def initialize
12
+ self.hourly = ManageIQ::ApplianceConsole::DatabaseMaintenanceHourly.new
13
+ self.periodic = ManageIQ::ApplianceConsole::DatabaseMaintenancePeriodic.new
14
+ self.requested_hourly_action = false
15
+ self.requested_periodic_action = false
16
+ self.executed_hourly_action = false
17
+ self.executed_periodic_action = false
18
+ end
19
+
20
+ def ask_questions
21
+ clear_screen
22
+ self.requested_hourly_action = hourly.confirm
23
+ self.requested_periodic_action = periodic.confirm
24
+ requested_hourly_action || requested_periodic_action
25
+ end
26
+
27
+ def activate
28
+ say("Configuring Database Maintenance...")
29
+ self.executed_hourly_action = hourly.activate
30
+ self.executed_periodic_action = periodic.activate
31
+ executed_hourly_action || executed_periodic_action
32
+ end
33
+ end # class DatabaseMaintenance < DatabaseConfiguration
34
+ end # module ApplianceConsole
35
+ end
@@ -0,0 +1,58 @@
1
+ require 'fileutils'
2
+
3
+ module ManageIQ
4
+ module ApplianceConsole
5
+ class DatabaseMaintenanceHourly
6
+ include ManageIQ::ApplianceConsole::Logging
7
+
8
+ HOURLY_CRON = "/etc/cron.hourly/miq-pg-maintenance-hourly.cron".freeze
9
+
10
+ attr_accessor :already_configured, :requested_deactivate, :requested_activate
11
+
12
+ def initialize
13
+ self.already_configured = File.exist?(HOURLY_CRON)
14
+ self.requested_deactivate = false
15
+ self.requested_activate = false
16
+ end
17
+
18
+ def activate
19
+ return deactivate if requested_deactivate
20
+ return configure if requested_activate
21
+ false
22
+ end
23
+
24
+ def confirm
25
+ if already_configured
26
+ self.requested_deactivate = agree("Hourly Database Maintenance is already configured, Un-Configure (Y/N):")
27
+ else
28
+ self.requested_activate = agree("Configure Hourly Database Maintenance? (Y/N): ")
29
+ end
30
+ end
31
+
32
+ private
33
+
34
+ def configure
35
+ say("Configuring Hourly Database Maintenance...")
36
+ write_hourly_cron
37
+ FileUtils.chmod(0755, HOURLY_CRON)
38
+ true
39
+ end
40
+
41
+ def deactivate
42
+ say("Un-Configuring Hourly Database Maintenance...")
43
+ FileUtils.rm_f(HOURLY_CRON)
44
+ true
45
+ end
46
+
47
+ def write_hourly_cron
48
+ File.open(HOURLY_CRON, "w") do |f|
49
+ f.write("#!/bin/sh\n")
50
+ f.write("/usr/bin/hourly_reindex_metrics_tables\n")
51
+ f.write("/usr/bin/hourly_reindex_miq_queue_table\n")
52
+ f.write("/usr/bin/hourly_reindex_miq_workers_table\n")
53
+ f.write("exit 0\n")
54
+ end
55
+ end
56
+ end # class DatabaseMaintenance < DatabaseConfiguration
57
+ end # module ApplianceConsole
58
+ end
@@ -0,0 +1,84 @@
1
+ module ManageIQ
2
+ module ApplianceConsole
3
+ class DatabaseMaintenancePeriodic
4
+ include ManageIQ::ApplianceConsole::Logging
5
+
6
+ RUN_AS = 'root'.freeze
7
+ PERIODIC_CMD = '/usr/bin/periodic_vacuum_full_tables'.freeze
8
+ CRONTAB_FILE = '/etc/crontab'.freeze
9
+ SCHEDULE_PROMPT = 'frequency periodic database maintenance should run (hourly daily weekly monthly)'.freeze
10
+ HOUR_PROMPT = 'hour number (0..23)'.freeze
11
+ WEEK_DAY_PROMPT = 'week day number (0..6, where Sunday is 0)'.freeze
12
+ MONTH_DAY_PROMPT = 'month day number (1..31)'.freeze
13
+
14
+ attr_accessor :crontab_schedule_expression, :already_configured, :requested_deactivate, :requested_activate
15
+
16
+ def initialize
17
+ self.crontab_schedule_expression = nil
18
+ self.already_configured = File.readlines(CRONTAB_FILE).detect { |line| line =~ /#{PERIODIC_CMD}/ }.present?
19
+ self.requested_deactivate = false
20
+ self.requested_activate = false
21
+ end
22
+
23
+ def activate
24
+ return deactivate if requested_deactivate
25
+ return configure if requested_activate
26
+ false
27
+ end
28
+
29
+ def confirm
30
+ if already_configured
31
+ self.requested_deactivate = agree("Periodic Database Maintenance is already configured, Un-Configure (Y/N):")
32
+ else
33
+ self.requested_activate = agree("Configure Periodic Database Maintenance? (Y/N): ")
34
+ ask_for_schedule if requested_activate
35
+ end
36
+ end
37
+
38
+ private
39
+
40
+ def ask_for_schedule
41
+ self.crontab_schedule_expression =
42
+ case ask_for_schedule_frequency(SCHEDULE_PROMPT, 'monthly')
43
+ when 'hourly'
44
+ generate_hourly_crontab_expression
45
+ when 'daily'
46
+ generate_daily_crontab_expression
47
+ when 'weekly'
48
+ generate_weekly_crontab_expression
49
+ when 'monthly'
50
+ generate_monthly_crontab_expression
51
+ end
52
+ end
53
+
54
+ def configure
55
+ File.open(CRONTAB_FILE, "a") do |f|
56
+ f.write("#{crontab_schedule_expression} #{RUN_AS} #{PERIODIC_CMD}\n")
57
+ end
58
+ true
59
+ end
60
+
61
+ def deactivate
62
+ keep_content = File.readlines(CRONTAB_FILE).reject { |line| line =~ /#{PERIODIC_CMD}/ }
63
+ File.open(CRONTAB_FILE, "w") { |f| keep_content.each { |line| f.puts line } }
64
+ true
65
+ end
66
+
67
+ def generate_hourly_crontab_expression
68
+ "0 * * * *"
69
+ end
70
+
71
+ def generate_daily_crontab_expression
72
+ "0 #{ask_for_hour_number(HOUR_PROMPT)} * * *"
73
+ end
74
+
75
+ def generate_weekly_crontab_expression
76
+ "0 #{ask_for_hour_number(HOUR_PROMPT)} * * #{ask_for_week_day_number(WEEK_DAY_PROMPT)}"
77
+ end
78
+
79
+ def generate_monthly_crontab_expression
80
+ "0 #{ask_for_hour_number(HOUR_PROMPT)} #{ask_for_month_day_number(MONTH_DAY_PROMPT)} * *"
81
+ end
82
+ end # class DatabaseMaintenancePeriodic
83
+ end # module ApplianceConsole
84
+ end