foreman_maintain 0.2.1 → 0.2.2

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 (41) hide show
  1. checksums.yaml +4 -4
  2. data/bin/foreman-maintain +2 -1
  3. data/definitions/checks/backup/directory_ready.rb +1 -1
  4. data/definitions/checks/restore/validate_backup.rb +77 -0
  5. data/definitions/checks/restore/validate_hostname.rb +20 -0
  6. data/definitions/features/foreman_proxy.rb +6 -1
  7. data/definitions/features/foreman_tasks.rb +5 -1
  8. data/definitions/features/instance.rb +8 -0
  9. data/definitions/features/katello.rb +0 -1
  10. data/definitions/features/mongo.rb +51 -12
  11. data/definitions/features/pulp.rb +2 -1
  12. data/definitions/features/service.rb +36 -0
  13. data/definitions/features/tar.rb +16 -4
  14. data/definitions/procedures/backup/online/pg_global_objects.rb +3 -1
  15. data/definitions/procedures/backup/prepare_directory.rb +1 -1
  16. data/definitions/procedures/backup/pulp.rb +2 -1
  17. data/definitions/procedures/pulp/migrate.rb +21 -0
  18. data/definitions/procedures/restore/candlepin_dump.rb +30 -0
  19. data/definitions/procedures/restore/configs.rb +40 -0
  20. data/definitions/procedures/restore/confirmation.rb +17 -0
  21. data/definitions/procedures/restore/drop_databases.rb +39 -0
  22. data/definitions/procedures/restore/extract_files.rb +70 -0
  23. data/definitions/procedures/restore/foreman_dump.rb +30 -0
  24. data/definitions/procedures/restore/installer_reset.rb +39 -0
  25. data/definitions/procedures/restore/mongo_dump.rb +41 -0
  26. data/definitions/procedures/restore/pg_global_objects.rb +36 -0
  27. data/definitions/procedures/restore/postgres_owner.rb +18 -0
  28. data/definitions/procedures/selinux/set_file_security.rb +22 -0
  29. data/definitions/procedures/service/daemon_reload.rb +18 -0
  30. data/definitions/procedures/service/restart.rb +2 -2
  31. data/definitions/scenarios/restore.rb +91 -0
  32. data/lib/foreman_maintain/cli.rb +3 -1
  33. data/lib/foreman_maintain/cli/restore_command.rb +26 -0
  34. data/lib/foreman_maintain/concerns/base_database.rb +35 -1
  35. data/lib/foreman_maintain/concerns/system_helpers.rb +1 -5
  36. data/lib/foreman_maintain/reporter/cli_reporter.rb +2 -1
  37. data/lib/foreman_maintain/utils/backup.rb +225 -0
  38. data/lib/foreman_maintain/utils/command_runner.rb +4 -2
  39. data/lib/foreman_maintain/utils/mongo_core.rb +49 -0
  40. data/lib/foreman_maintain/version.rb +1 -1
  41. metadata +20 -2
@@ -7,6 +7,7 @@ require 'foreman_maintain/cli/upgrade_command'
7
7
  require 'foreman_maintain/cli/backup_command'
8
8
  require 'foreman_maintain/cli/advanced_command'
9
9
  require 'foreman_maintain/cli/service_command'
10
+ require 'foreman_maintain/cli/restore_command'
10
11
 
11
12
  module ForemanMaintain
12
13
  module Cli
@@ -15,9 +16,10 @@ module ForemanMaintain
15
16
 
16
17
  subcommand 'health', 'Health related commands', HealthCommand
17
18
  subcommand 'upgrade', 'Upgrade related commands', UpgradeCommand
19
+ subcommand 'service', 'Control applicable services', ServiceCommand
18
20
  subcommand 'backup', 'Backup server', BackupCommand
21
+ subcommand 'restore', 'Restore a backup', RestoreCommand
19
22
  subcommand 'advanced', 'Advanced tools for server maintenance', AdvancedCommand
20
- subcommand 'service', 'Control applicable services', ServiceCommand
21
23
 
22
24
  def run(*arguments)
23
25
  logger.info("Running foreman-maintain command with arguments #{arguments.inspect}")
@@ -0,0 +1,26 @@
1
+ module ForemanMaintain
2
+ module Cli
3
+ class RestoreCommand < Base
4
+ interactive_option
5
+ parameter 'BACKUP_DIR', 'Path to backup directory to restore',
6
+ :attribute_name => :backup_dir
7
+
8
+ option ['-i', '--incremental'], :flag, 'Restore an incremental backup',
9
+ :attribute_name => :incremental
10
+
11
+ def execute
12
+ scenario = Scenarios::Restore.new(
13
+ :backup_dir => @backup_dir,
14
+ :incremental_backup => @incremental || incremental_backup?
15
+ )
16
+ run_scenario(scenario)
17
+ exit runner.exit_code
18
+ end
19
+
20
+ def incremental_backup?
21
+ backup = ForemanMaintain::Utils::Backup.new(@backup_dir)
22
+ backup.metadata.fetch(:incremental, false)
23
+ end
24
+ end
25
+ end
26
+ end
@@ -52,13 +52,34 @@ module ForemanMaintain
52
52
  execute!(dump_command(config) + " > #{file}", :hidden_patterns => [config['password']])
53
53
  end
54
54
 
55
+ def restore_dump(file, localdb, config = configuration)
56
+ if localdb
57
+ dump_cmd = "runuser - postgres -c 'pg_restore -C -d postgres #{file}'"
58
+ execute!(dump_cmd)
59
+ else
60
+ # TODO: figure out how to completely ignore errors. Currently this
61
+ # sometimes exits with 1 even though errors are ignored by pg_restore
62
+ dump_cmd = base_command(config, 'pg_restore') +
63
+ ' --no-privileges --clean --disable-triggers -n public ' \
64
+ "-d #{config['database']} #{file}"
65
+ execute!(dump_cmd, :hidden_patterns => [config['password']],
66
+ :valid_exit_statuses => [0, 1])
67
+ end
68
+ end
69
+
70
+ def restore_pg_globals(pg_globals, config = configuration)
71
+ execute!(base_command(config, 'psql') + " -f #{pg_globals} postgres 2>/dev/null",
72
+ :hidden_patterns => [config['password']])
73
+ end
74
+
55
75
  def backup_local(backup_file, extra_tar_options = {})
56
76
  dir = extra_tar_options.fetch(:data_dir, data_dir)
57
77
  FileUtils.cd(dir) do
58
78
  tar_options = {
59
79
  :archive => backup_file,
60
80
  :command => 'create',
61
- :transform => 's,^,var/lib/pgsql/data/,S'
81
+ :transform => 's,^,var/lib/pgsql/data/,S',
82
+ :files => '*'
62
83
  }.merge(extra_tar_options)
63
84
  feature(:tar).run(tar_options)
64
85
  end
@@ -112,6 +133,19 @@ module ForemanMaintain
112
133
  find_dir_containing_file(directory, 'postgresql.conf')
113
134
  end
114
135
 
136
+ def dropdb(config = configuration)
137
+ if local?
138
+ execute!("runuser - postgres -c 'dropdb #{config['database']}'")
139
+ else
140
+ delete_statement = psql(<<-SQL)
141
+ select string_agg('drop table if exists \"' || tablename || '\" cascade;', '')
142
+ from pg_tables
143
+ where schemaname = 'public';
144
+ SQL
145
+ psql(delete_statement)
146
+ end
147
+ end
148
+
115
149
  private
116
150
 
117
151
  def base_command(config, command = 'psql')
@@ -7,6 +7,7 @@ module ForemanMaintain
7
7
  module Concerns
8
8
  module SystemHelpers
9
9
  include Logger
10
+ include Concerns::Finders
10
11
 
11
12
  def self.included(klass)
12
13
  klass.extend(self)
@@ -184,11 +185,6 @@ module ForemanMaintain
184
185
  execute("lvs --noheadings -o lv_path -S lv_name=#{lv_name}").strip
185
186
  end
186
187
 
187
- def local_psql_database?
188
- (feature(:foreman_database) && feature(:foreman_database).local?) ||
189
- (feature(:candlepin_database) && feature(:candlepin_database).local?)
190
- end
191
-
192
188
  def find_dir_containing_file(directory, target)
193
189
  result = nil
194
190
  Find.find(directory) do |path|
@@ -88,7 +88,8 @@ module ForemanMaintain
88
88
  end
89
89
 
90
90
  def before_execution_starts(execution)
91
- logger.info("--- Execution step '#{execution.name}' started ---")
91
+ label = execution.step.label.to_s.tr('_', '-')
92
+ logger.info("--- Execution step '#{execution.name}' [#{label}] started ---")
92
93
  puts(execution_info(execution, ''))
93
94
  end
94
95
 
@@ -0,0 +1,225 @@
1
+ require 'zlib'
2
+ require 'rubygems/package'
3
+ require 'yaml'
4
+
5
+ module ForemanMaintain
6
+ module Utils
7
+ class Backup
8
+ include Concerns::SystemHelpers
9
+
10
+ attr_accessor :standard_files, :katello_online_files, :katello_offline_files,
11
+ :foreman_online_files, :foreman_offline_files, :fpc_offline_files,
12
+ :fpc_online_files
13
+
14
+ def initialize(backup_dir)
15
+ # fpc stands for foreman proxy w/ content
16
+ @backup_dir = backup_dir
17
+ @standard_files = ['config_files.tar.gz']
18
+ @katello_online_files = ['mongo_dump', 'candlepin.dump', 'foreman.dump']
19
+ @katello_offline_files = ['mongo_data.tar.gz', 'pgsql_data.tar.gz']
20
+ @foreman_online_files = ['foreman.dump']
21
+ @foreman_offline_files = ['pgsql_data.tar.gz']
22
+ @fpc_online_files = ['mongo_dump']
23
+ @fpc_offline_files = ['mongo_data.tar.gz']
24
+ end
25
+
26
+ def file_map
27
+ @file_map ||= {
28
+ :mongo_data => map_file(@backup_dir, 'mongo_data.tar.gz'),
29
+ :pgsql_data => map_file(@backup_dir, 'pgsql_data.tar.gz'),
30
+ :pulp_data => map_file(@backup_dir, 'pulp_data.tar'),
31
+ :foreman_dump => map_file(@backup_dir, 'foreman.dump'),
32
+ :candlepin_dump => map_file(@backup_dir, 'candlepin.dump'),
33
+ :mongo_dump => map_file(@backup_dir, 'mongo_dump'),
34
+ :config_files => map_file(@backup_dir, 'config_files.tar.gz'),
35
+ :pg_globals => map_file(@backup_dir, 'pg_globals.dump'),
36
+ :metadata => map_file(@backup_dir, 'metadata.yml')
37
+ }
38
+ end
39
+
40
+ def map_file(backup_dir, filename)
41
+ file_path = File.join(backup_dir, filename)
42
+ present = File.exist?(file_path)
43
+ {
44
+ :present => present,
45
+ :path => file_path
46
+ }
47
+ end
48
+
49
+ def present_files
50
+ present_files = file_map.select { |_k, v| v[:present] }
51
+ present_files.values.map { |f| File.basename(f[:path]) }
52
+ end
53
+
54
+ def valid_backup?
55
+ file_map[:config_files][:present] && check_backup
56
+ end
57
+
58
+ def check_backup
59
+ if feature(:instance).foreman_proxy_with_content?
60
+ valid_fpc_backup?
61
+ elsif feature(:katello)
62
+ valid_katello_backup?
63
+ else
64
+ valid_foreman_backup?
65
+ end
66
+ end
67
+
68
+ def valid_fpc_backup?
69
+ fpc_online_backup? || fpc_standard_backup? || fpc_logical_backup?
70
+ end
71
+
72
+ def valid_katello_backup?
73
+ katello_online_backup? || katello_standard_backup? || katello_logical_backup?
74
+ end
75
+
76
+ def valid_foreman_backup?
77
+ foreman_standard_backup? || foreman_online_backup? || foreman_logical_backup?
78
+ end
79
+
80
+ def katello_standard_backup?
81
+ file_map[:mongo_data][:present] &&
82
+ file_map[:pgsql_data][:present] &&
83
+ !(
84
+ file_map[:candlepin_dump][:present] ||
85
+ file_map[:foreman_dump][:present] ||
86
+ file_map[:mongo_dump][:present]
87
+ )
88
+ end
89
+
90
+ def katello_online_backup?
91
+ file_map[:candlepin_dump][:present] &&
92
+ file_map[:foreman_dump][:present] &&
93
+ file_map[:mongo_dump][:present] &&
94
+ !(
95
+ file_map[:mongo_data][:present] ||
96
+ file_map[:pgsql_data][:present]
97
+ )
98
+ end
99
+
100
+ def katello_logical_backup?
101
+ file_map[:mongo_dump][:present] &&
102
+ file_map[:mongo_data][:present] &&
103
+ file_map[:pgsql_data][:present] &&
104
+ file_map[:candlepin_dump][:present] &&
105
+ file_map[:foreman_dump][:present]
106
+ end
107
+
108
+ def fpc_standard_backup?
109
+ file_map[:mongo_data][:present] &&
110
+ !(
111
+ file_map[:pgsql_data][:present] ||
112
+ file_map[:candlepin_dump][:present] ||
113
+ file_map[:foreman_dump][:present] ||
114
+ file_map[:mongo_dump][:present]
115
+ )
116
+ end
117
+
118
+ def fpc_online_backup?
119
+ file_map[:mongo_dump][:present] &&
120
+ !(
121
+ file_map[:mongo_data][:present] ||
122
+ file_map[:pgsql_data][:present] ||
123
+ file_map[:candlepin_dump][:present] ||
124
+ file_map[:foreman_dump][:present]
125
+ )
126
+ end
127
+
128
+ def fpc_logical_backup?
129
+ file_map[:mongo_dump][:present] &&
130
+ file_map[:mongo_data][:present] &&
131
+ !(
132
+ file_map[:pgsql_data][:present] ||
133
+ file_map[:candlepin_dump][:present] ||
134
+ file_map[:foreman_dump][:present]
135
+ )
136
+ end
137
+
138
+ def foreman_standard_backup?
139
+ file_map[:pgsql_data][:present] &&
140
+ !(
141
+ file_map[:candlepin_dump][:present] ||
142
+ file_map[:foreman_dump][:present] ||
143
+ file_map[:mongo_data][:present] ||
144
+ file_map[:mongo_dump][:present]
145
+ )
146
+ end
147
+
148
+ def foreman_online_backup?
149
+ file_map[:foreman_dump][:present] &&
150
+ !(
151
+ file_map[:candlepin_dump][:present] ||
152
+ file_map[:pgsql_data][:present] ||
153
+ file_map[:mongo_data][:present] ||
154
+ file_map[:mongo_dump][:present]
155
+ )
156
+ end
157
+
158
+ def foreman_logical_backup?
159
+ file_map[:pgsql_data][:present] &&
160
+ file_map[:foreman_dump][:present] &&
161
+ !(
162
+ file_map[:candlepin_dump][:present] ||
163
+ file_map[:mongo_data][:present] ||
164
+ file_map[:mongo_dump][:present]
165
+ )
166
+ end
167
+
168
+ def validate_hostname?
169
+ # make sure that the system hostname is the same as the backup
170
+ config_tarball = file_map[:config_files][:path]
171
+ config_files = tarball_file_list(config_tarball)
172
+
173
+ # Incremental backups sometimes don't include httpd.conf. Since a "base" backup
174
+ # is restored before an incremental, we can assume that the hostname is checked
175
+ # during the base backup restore
176
+ if config_files.include?('etc/httpd/conf/httpd.conf')
177
+ tar_cmd = "tar zxf #{config_tarball} etc/httpd/conf/httpd.conf " \
178
+ "--to-stdout | grep ServerName | awk {'print $2'} | tr -d '\"'"
179
+ backup_hostname = execute(tar_cmd).chomp
180
+ backup_hostname == hostname
181
+ else
182
+ true
183
+ end
184
+ end
185
+
186
+ def metadata
187
+ if file_map[:metadata][:present]
188
+ YAML.load_file(file_map[:metadata][:path])
189
+ else
190
+ {}
191
+ end
192
+ end
193
+
194
+ def pulp_tar_split?
195
+ File.exist?(File.join(@backup_dir, 'pulp_data.part0002'))
196
+ end
197
+
198
+ def tar_backups_exist?
199
+ file_map[:mongo_data][:present] ||
200
+ file_map[:pulp_data][:present] ||
201
+ file_map[:pgsql_data][:present]
202
+ end
203
+
204
+ def sql_dump_files_exist?
205
+ file_map[:foreman_dump][:present] ||
206
+ file_map[:candlepin_dump][:present]
207
+ end
208
+
209
+ private
210
+
211
+ def tarball_file_list(tarball)
212
+ # accepts tar.gz files only
213
+ file_list = []
214
+ File.open(tarball, 'rb') do |file|
215
+ ::Zlib::GzipReader.wrap(file) do |gz|
216
+ ::Gem::Package::TarReader.new(gz) do |tar|
217
+ tar.each { |entry| file_list << entry.full_name }
218
+ end
219
+ end
220
+ end
221
+ file_list
222
+ end
223
+ end
224
+ end
225
+ end
@@ -8,13 +8,15 @@ module ForemanMaintain
8
8
  attr_reader :logger, :command
9
9
 
10
10
  def initialize(logger, command, options)
11
- options.validate_options!(:stdin, :hidden_patterns, :interactive)
11
+ options.validate_options!(:stdin, :hidden_patterns, :interactive, :valid_exit_statuses)
12
+ options[:valid_exit_statuses] ||= [0]
12
13
  @logger = logger
13
14
  @command = command
14
15
  @stdin = options[:stdin]
15
16
  @hidden_patterns = Array(options[:hidden_patterns])
16
17
  @interactive = options[:interactive]
17
18
  @options = options
19
+ @valid_exit_statuses = options[:valid_exit_statuses]
18
20
  raise ArgumentError, 'Can not pass stdin for interactive command' if @interactive && @stdin
19
21
  end
20
22
 
@@ -43,7 +45,7 @@ module ForemanMaintain
43
45
  end
44
46
 
45
47
  def success?
46
- exit_status == 0
48
+ @valid_exit_statuses.include? exit_status
47
49
  end
48
50
 
49
51
  def execution_error
@@ -15,6 +15,10 @@ module ForemanMaintain::Utils
15
15
  def dump_command
16
16
  'mongodump'
17
17
  end
18
+
19
+ def restore_command
20
+ 'mongorestore'
21
+ end
18
22
  end
19
23
 
20
24
  class MongoCore34 < MongoCore
@@ -33,5 +37,50 @@ module ForemanMaintain::Utils
33
37
  def dump_command
34
38
  'scl enable rh-mongodb34 -- mongodump'
35
39
  end
40
+
41
+ def restore_command
42
+ 'scl enable rh-mongodb34 -- mongorestore'
43
+ end
44
+ end
45
+
46
+ class MongoCoreInstalled < MongoCore
47
+ include ForemanMaintain::Concerns::SystemHelpers
48
+
49
+ attr_reader :services, :server_config_files, :client_command, :dump_command
50
+
51
+ def initialize
52
+ @services = {}
53
+ @server_config_files = []
54
+
55
+ detect_mongo_default
56
+ detect_mongo_34
57
+ raise ForemanMaintain::Error::Fail, 'Mongo client was not found' unless @client_command
58
+ end
59
+
60
+ private
61
+
62
+ def detect_mongo_34
63
+ if find_package('rh-mongodb34-mongodb-server')
64
+ @services['rh-mongodb34-mongod'] = 5
65
+ @server_config_files << '/etc/opt/rh/rh-mongodb34/mongod.conf'
66
+ end
67
+
68
+ if find_package('rh-mongodb34-mongodb')
69
+ @client_command = 'scl enable rh-mongodb34 -- mongo'
70
+ @dump_command = 'scl enable rh-mongodb34 -- mongodump'
71
+ end
72
+ end
73
+
74
+ def detect_mongo_default
75
+ if find_package('mongodb-server')
76
+ @services['mongod'] = 5
77
+ @server_config_files << '/etc/mongod.conf'
78
+ end
79
+
80
+ if find_package('mongodb')
81
+ @client_command = 'mongo'
82
+ @dump_command = 'mongodump'
83
+ end
84
+ end
36
85
  end
37
86
  end