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,94 @@
1
+ require 'linux_admin'
2
+ require 'fileutils'
3
+
4
+ module ManageIQ
5
+ module ApplianceConsole
6
+ class LogicalVolumeManagement
7
+ include ManageIQ::ApplianceConsole::Logging
8
+
9
+ # Required instantiation parameters
10
+ attr_accessor :disk, :mount_point, :name
11
+
12
+ # Derived or optionally provided instantiation parameters
13
+ attr_accessor :volume_group_name, :filesystem_type, :logical_volume_path
14
+
15
+ # Logical Disk creation objects
16
+ attr_reader :logical_volume, :partition, :physical_volume, :volume_group
17
+
18
+ def initialize(options = {})
19
+ # Required instantiation parameters
20
+ self.disk = options[:disk] || raise(ArgumentError, "disk object required")
21
+ self.mount_point = options[:mount_point] || raise(ArgumentError, "mount point required")
22
+ self.name = options[:name] || raise(ArgumentError, "unique name required")
23
+
24
+ # Derived or optionally provided instantiation parameters
25
+ self.volume_group_name ||= "vg_#{name}"
26
+ self.filesystem_type ||= "xfs"
27
+ self.logical_volume_path ||= "/dev/#{volume_group_name}/lv_#{name}"
28
+ end
29
+
30
+ # Helper method
31
+ def setup
32
+ create_partition_to_fill_disk
33
+ create_physical_volume
34
+ create_volume_group
35
+ create_logical_volume_to_fill_volume_group
36
+ format_logical_volume
37
+ mount_disk
38
+ update_fstab
39
+ end
40
+
41
+ private
42
+
43
+ def create_partition_to_fill_disk
44
+ disk.create_partition_table
45
+ AwesomeSpawn.run!("parted -s #{disk.path} mkpart primary 0% 100%")
46
+
47
+ self.disk = LinuxAdmin::Disk.local.find { |d| d.path == disk.path }
48
+ @partition = disk.partitions.first
49
+ end
50
+
51
+ def create_physical_volume
52
+ @physical_volume = LinuxAdmin::PhysicalVolume.create(partition)
53
+ end
54
+
55
+ def create_volume_group
56
+ @volume_group = LinuxAdmin::VolumeGroup.create(volume_group_name, physical_volume)
57
+ end
58
+
59
+ def create_logical_volume_to_fill_volume_group
60
+ @logical_volume = LinuxAdmin::LogicalVolume.create(logical_volume_path, volume_group, 100)
61
+ end
62
+
63
+ def format_logical_volume
64
+ AwesomeSpawn.run!("mkfs.#{filesystem_type} #{logical_volume.path}")
65
+ end
66
+
67
+ def mount_disk
68
+ if mount_point.symlink?
69
+ FileUtils.rm_rf(mount_point)
70
+ FileUtils.mkdir_p(mount_point)
71
+ end
72
+ AwesomeSpawn.run!("mount", :params => {"-t" => filesystem_type,
73
+ nil => [logical_volume.path, mount_point]})
74
+ end
75
+
76
+ def update_fstab
77
+ fstab = LinuxAdmin::FSTab.instance
78
+ return if fstab.entries.find { |e| e.mount_point == mount_point }
79
+
80
+ entry = LinuxAdmin::FSTabEntry.new(
81
+ :device => logical_volume.path,
82
+ :mount_point => mount_point,
83
+ :fs_type => filesystem_type,
84
+ :mount_options => "rw,noatime",
85
+ :dumpable => 0,
86
+ :fsck_order => 0
87
+ )
88
+
89
+ fstab.entries << entry
90
+ fstab.write! # Test this more, whitespace is removed
91
+ end
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,46 @@
1
+ require 'awesome_spawn'
2
+
3
+ module ManageIQ
4
+ module ApplianceConsole
5
+ # Kerberos principal
6
+ class Principal
7
+ attr_accessor :ca_name
8
+ attr_accessor :hostname
9
+ attr_accessor :realm
10
+ attr_accessor :service
11
+ # kerberos principal name
12
+ attr_accessor :name
13
+
14
+ def initialize(options = {})
15
+ options.each { |n, v| public_send("#{n}=", v) }
16
+ @ca_name ||= "ipa"
17
+ @realm = @realm.upcase if @realm
18
+ @name ||= "#{service}/#{hostname}@#{realm}"
19
+ end
20
+
21
+ def register
22
+ request if ipa? && !exist?
23
+ end
24
+
25
+ def subject_name
26
+ "CN=#{hostname},OU=#{service},O=#{realm}"
27
+ end
28
+
29
+ def ipa?
30
+ @ca_name == "ipa"
31
+ end
32
+
33
+ private
34
+
35
+ def exist?
36
+ AwesomeSpawn.run("/usr/bin/ipa", :params => ["-e", "skip_version_check=1", "service-find", "--principal", name]).success?
37
+ end
38
+
39
+ def request
40
+ # using --force because these services tend not to be in dns
41
+ # this is like VERIFY_NONE
42
+ AwesomeSpawn.run!("/usr/bin/ipa", :params => ["-e", "skip_version_check=1", "service-add", "--force", name])
43
+ end
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,211 @@
1
+ require 'resolv'
2
+
3
+ module ManageIQ
4
+ module ApplianceConsole
5
+ CANCEL = 'Cancel'.freeze
6
+
7
+ module Prompts
8
+ CLEAR_CODE = `clear`
9
+ IPV4_REGEXP = Resolv::IPv4::Regex
10
+ IPV6_REGEXP = Resolv::IPv6::Regex
11
+ IP_REGEXP = Regexp.union(Resolv::IPv4::Regex, Resolv::IPv6::Regex).freeze
12
+ DOMAIN_REGEXP = /^[a-z0-9]+([\-\.]{1}[a-z0-9]+)*(\.[a-z]{2,13})?$/
13
+ INT_REGEXP = /^[0-9]+$/
14
+ NONE_REGEXP = /^('?NONE'?)?$/i.freeze
15
+ HOSTNAME_REGEXP = /^(([a-zA-Z]|[a-zA-Z][a-zA-Z0-9\-]*[a-zA-Z0-9])\.)*([A-Za-z]|[A-Za-z][A-Za-z0-9\-]*[A-Za-z0-9])$/
16
+
17
+ SAMPLE_URLS = {
18
+ 'nfs' => 'nfs://host.mydomain.com/exported/my_exported_folder/db.backup',
19
+ 'smb' => 'smb://host.mydomain.com/my_share/daily_backup/db.backup',
20
+ }
21
+
22
+ def sample_url(scheme)
23
+ SAMPLE_URLS[scheme]
24
+ end
25
+
26
+ def ask_for_uri(prompt, expected_scheme)
27
+ require 'uri'
28
+ just_ask(prompt, nil, nil, 'a valid URI') do |q|
29
+ q.validate = lambda do |a|
30
+ # Convert all backslashes in the URI to forward slashes and strip whitespace
31
+ a.tr!('\\', '/')
32
+ a.strip!
33
+ u = URI(a)
34
+ # validate it has a hostname/ip and a share
35
+ u.scheme == expected_scheme &&
36
+ (u.host =~ HOSTNAME_REGEXP || u.hostname =~ IP_REGEXP) &&
37
+ !u.path.empty?
38
+ end
39
+ end
40
+ end
41
+
42
+ def press_any_key
43
+ say("\nPress any key to continue.")
44
+ begin
45
+ system("stty raw -echo")
46
+ STDIN.getc
47
+ ensure
48
+ system("stty -raw echo")
49
+ end
50
+ end
51
+
52
+ def clear_screen
53
+ print CLEAR_CODE
54
+ end
55
+
56
+ def are_you_sure?(clarifier = nil)
57
+ clarifier = " you want to #{clarifier}" if clarifier && !clarifier.include?("want")
58
+ agree("Are you sure#{clarifier}? (Y/N): ")
59
+ end
60
+
61
+ def ask_yn?(prompt, default = nil)
62
+ ask("#{prompt}? (Y/N): ") do |q|
63
+ q.default = default if default
64
+ q.validate = ->(p) { (p.blank? && default) || %w(y n).include?(p.downcase[0]) }
65
+ q.responses[:not_valid] = "Please provide yes or no."
66
+ end.downcase[0] == 'y'
67
+ end
68
+
69
+ def ask_for_domain(prompt, default = nil, validate = DOMAIN_REGEXP, error_text = "a valid Domain.", &block)
70
+ just_ask(prompt, default, validate, error_text, &block)
71
+ end
72
+
73
+ def ask_for_ip(prompt, default, validate = IP_REGEXP, error_text = "a valid IP Address.", &block)
74
+ just_ask(prompt, default, validate, error_text, &block)
75
+ end
76
+
77
+ def ask_for_ipv4(prompt, default)
78
+ ask_for_ip(prompt, default, IPV4_REGEXP)
79
+ end
80
+
81
+ def ask_for_ipv4_or_none(prompt, default = nil)
82
+ ask_for_ip(prompt, default, Regexp.union(NONE_REGEXP, IPV4_REGEXP)).gsub(NONE_REGEXP, "")
83
+ end
84
+
85
+ def ask_for_ipv6(prompt, default)
86
+ ask_for_ip(prompt, default, IPV6_REGEXP)
87
+ end
88
+
89
+ def ask_for_ipv6_or_none(prompt, default = nil)
90
+ ask_for_ip(prompt, default, Regexp.union(IPV6_REGEXP, NONE_REGEXP)).gsub(NONE_REGEXP, '')
91
+ end
92
+
93
+ def ask_for_hostname(prompt, default = nil, validate = HOSTNAME_REGEXP, error_text = "a valid Hostname.", &block)
94
+ just_ask(prompt, default, validate, error_text, &block)
95
+ end
96
+
97
+ def ask_for_ip_or_hostname(prompt, default = nil)
98
+ validation = ->(h) { (h =~ HOSTNAME_REGEXP || h =~ IP_REGEXP) && h.length > 0 }
99
+ ask_for_ip(prompt, default, validation, "a valid Hostname or IP Address.")
100
+ end
101
+
102
+ def ask_for_ip_or_hostname_or_none(prompt, default = nil)
103
+ validation = Regexp.union(NONE_REGEXP, HOSTNAME_REGEXP, IP_REGEXP)
104
+ ask_for_ip(prompt, default, validation, "a valid Hostname or IP Address.").gsub(NONE_REGEXP, "")
105
+ end
106
+
107
+ def ask_for_schedule_frequency(prompt, default = nil)
108
+ validation = ->(h) { %w(hourly daily weekly monthly).include?(h) }
109
+ just_ask(prompt, default, validation, "hourly, daily, weekly or monthly")
110
+ end
111
+
112
+ def ask_for_hour_number(prompt)
113
+ ask_for_integer(prompt, (0..23))
114
+ end
115
+
116
+ def ask_for_week_day_number(prompt)
117
+ ask_for_integer(prompt, (0..6))
118
+ end
119
+
120
+ def ask_for_month_day_number(prompt)
121
+ ask_for_integer(prompt, (1..31))
122
+ end
123
+
124
+ def ask_for_many(prompt, collective = nil, default = nil, max_length = 255, max_count = 6)
125
+ collective ||= "#{prompt}s"
126
+ validate = ->(p) { (p.length < max_length) && (p.split(/[\s,;]+/).length <= max_count) }
127
+ error_message = "up to #{max_count} #{prompt}s separated by a space and up to #{max_length} characters"
128
+ just_ask(collective, default, validate, error_message).split(/[\s,;]+/).collect(&:strip)
129
+ end
130
+
131
+ def ask_for_password(prompt, default = nil)
132
+ pass = just_ask(prompt, default.present? ? "********" : nil) do |q|
133
+ q.echo = '*'
134
+ yield q if block_given?
135
+ end
136
+ pass == "********" ? (default || "") : pass
137
+ end
138
+
139
+ def ask_for_string(prompt, default = nil)
140
+ just_ask(prompt, default)
141
+ end
142
+
143
+ def ask_for_integer(prompt, range = nil, default = nil)
144
+ just_ask(prompt, default, INT_REGEXP, "an integer", Integer) { |q| q.in = range if range }
145
+ end
146
+
147
+ def ask_for_disk(disk_name, verify = true)
148
+ require "linux_admin"
149
+ disks = LinuxAdmin::Disk.local.select { |d| d.partitions.empty? }
150
+
151
+ if disks.empty?
152
+ say "No partition found for #{disk_name}. You probably want to add an unpartitioned disk and try again."
153
+ else
154
+ default_choice = disks.size == 1 ? "1" : nil
155
+ disk = ask_with_menu(
156
+ disk_name,
157
+ disks.collect { |d| [("#{d.path}: #{d.size.to_i / 1.megabyte} MB"), d] },
158
+ default_choice
159
+ ) do |q|
160
+ q.choice("Don't partition the disk") { nil }
161
+ end
162
+ end
163
+
164
+ if verify && disk.nil?
165
+ say ""
166
+ raise MiqSignalError unless are_you_sure?(" you don't want to partition the #{disk_name}")
167
+ end
168
+ disk
169
+ end
170
+
171
+ # use the integer index for menu prompts
172
+ # ensure default is a string
173
+ def default_to_index(default, options)
174
+ return unless default
175
+ default_index = if options.kind_of?(Hash)
176
+ options.values.index(default) || options.keys.index(default)
177
+ else
178
+ options.index(default)
179
+ end
180
+ default_index ? (default_index.to_i + 1).to_s : default.to_s
181
+ end
182
+
183
+ def ask_with_menu(prompt, options, default = nil, clear_screen_after = true)
184
+ say("#{prompt}\n\n")
185
+
186
+ default = default_to_index(default, options)
187
+ selection = nil
188
+ choose do |menu|
189
+ menu.default = default
190
+ menu.index = :number
191
+ menu.index_suffix = ") "
192
+ menu.prompt = "\nChoose the #{prompt.downcase}:#{" |#{default}|" if default} "
193
+ options.each { |o, v| menu.choice(o) { |c| selection = v || c } }
194
+ yield menu if block_given?
195
+ end
196
+ clear_screen if clear_screen_after
197
+ selection
198
+ end
199
+
200
+ def just_ask(prompt, default = nil, validate = nil, error_text = nil, klass = nil)
201
+ ask("Enter the #{prompt}: ", klass) do |q|
202
+ q.readline = true
203
+ q.default = default.to_s if default
204
+ q.validate = validate if validate
205
+ q.responses[:not_valid] = error_text ? "Please provide #{error_text}" : "Please provide in the specified format"
206
+ yield q if block_given?
207
+ end
208
+ end
209
+ end
210
+ end
211
+ end
@@ -0,0 +1,53 @@
1
+ require 'linux_admin'
2
+
3
+ module ManageIQ
4
+ module ApplianceConsole
5
+ class Scap
6
+ def initialize(rules_dir)
7
+ @rules_dir = rules_dir
8
+ end
9
+
10
+ def lockdown
11
+ if packages_installed? && config_exists?
12
+ say("Locking down the appliance for SCAP...")
13
+ require 'yaml'
14
+ scap_config = YAML.load_file(yaml_filename)
15
+ begin
16
+ LinuxAdmin::Scap.new("rhel7").lockdown(*scap_config['rules'], scap_config['values'])
17
+ rescue => e
18
+ say("Configuration failed: #{e.message}")
19
+ else
20
+ say("Complete")
21
+ end
22
+ end
23
+ end
24
+
25
+ private
26
+
27
+ def yaml_filename
28
+ File.expand_path("scap_rules.yml", @rules_dir)
29
+ end
30
+
31
+ def packages_installed?
32
+ if !LinuxAdmin::Scap.openscap_available?
33
+ say("OpenSCAP has not been installed")
34
+ false
35
+ elsif !LinuxAdmin::Scap.ssg_available?("rhel7")
36
+ say("SCAP Security Guide has not been installed")
37
+ false
38
+ else
39
+ true
40
+ end
41
+ end
42
+
43
+ def config_exists?
44
+ if File.exist?(yaml_filename)
45
+ true
46
+ else
47
+ say("SCAP rules configuration file missing")
48
+ false
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,79 @@
1
+ module ManageIQ
2
+ module ApplianceConsole
3
+ class TempStorageConfiguration
4
+ TEMP_DISK_FILESYSTEM_TYPE = "xfs".freeze
5
+ TEMP_DISK_MOUNT_POINT = Pathname.new("/var/www/miq_tmp").freeze
6
+ TEMP_DISK_MOUNT_OPTS = "rw,noatime,nobarrier".freeze
7
+
8
+ attr_reader :disk
9
+
10
+ include ManageIQ::ApplianceConsole::Logging
11
+
12
+ def initialize(config = {})
13
+ @disk = config[:disk]
14
+ end
15
+
16
+ def activate
17
+ say("Configuring #{disk.path} as temp storage...")
18
+ add_temp_disk(disk)
19
+ end
20
+
21
+ def ask_questions
22
+ @disk = ask_for_disk("temp storage disk", false)
23
+ disk && are_you_sure?("configure #{disk.path} as temp storage")
24
+ end
25
+
26
+ def add_temp_disk(disk)
27
+ log_and_feedback(__method__) do
28
+ partition = create_partition_to_fill_disk(disk)
29
+ format_partition(partition)
30
+ mount_temp_disk(partition)
31
+ update_fstab(partition)
32
+ end
33
+ end
34
+
35
+ def format_partition(partition)
36
+ AwesomeSpawn.run!("mkfs.#{TEMP_DISK_FILESYSTEM_TYPE} #{partition.path}")
37
+ end
38
+
39
+ def mount_temp_disk(partition)
40
+ # TODO: should this be moved into LinuxAdmin?
41
+ FileUtils.rm_rf(TEMP_DISK_MOUNT_POINT)
42
+ FileUtils.mkdir_p(TEMP_DISK_MOUNT_POINT)
43
+ AwesomeSpawn.run!("mount", :params => {
44
+ "-t" => TEMP_DISK_FILESYSTEM_TYPE,
45
+ "-o" => TEMP_DISK_MOUNT_OPTS,
46
+ nil => [partition.path, TEMP_DISK_MOUNT_POINT]
47
+ })
48
+ end
49
+
50
+ def update_fstab(partition)
51
+ fstab = LinuxAdmin::FSTab.instance
52
+ return if fstab.entries.detect { |e| e.mount_point == TEMP_DISK_MOUNT_POINT }
53
+
54
+ entry = LinuxAdmin::FSTabEntry.new(
55
+ :device => partition.path,
56
+ :mount_point => TEMP_DISK_MOUNT_POINT,
57
+ :fs_type => TEMP_DISK_FILESYSTEM_TYPE,
58
+ :mount_options => TEMP_DISK_MOUNT_OPTS,
59
+ :dumpable => 0,
60
+ :fsck_order => 0
61
+ )
62
+
63
+ fstab.entries << entry
64
+ fstab.write! # Test this more, whitespace is removed
65
+ end
66
+
67
+ # FIXME: Copied from InternalDatabaseConfiguration - remove both when LinuxAdmin updated
68
+ def create_partition_to_fill_disk(disk)
69
+ # @disk.create_partition('primary', '100%')
70
+ disk.create_partition_table # LinuxAdmin::Disk.create_partition has this already...
71
+ AwesomeSpawn.run!("parted -s #{disk.path} mkpart primary 0% 100%")
72
+
73
+ # FIXME: Refetch the disk after creating the partition
74
+ disk = LinuxAdmin::Disk.local.find { |d| d.path == disk.path }
75
+ disk.partitions.first
76
+ end
77
+ end # class TempStorageConfiguration
78
+ end # module ApplianceConsole
79
+ end