manageiq-appliance_console 1.0.0

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