foreman_maintain 0.2.1 → 0.2.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/bin/foreman-maintain +2 -1
- data/definitions/checks/backup/directory_ready.rb +1 -1
- data/definitions/checks/restore/validate_backup.rb +77 -0
- data/definitions/checks/restore/validate_hostname.rb +20 -0
- data/definitions/features/foreman_proxy.rb +6 -1
- data/definitions/features/foreman_tasks.rb +5 -1
- data/definitions/features/instance.rb +8 -0
- data/definitions/features/katello.rb +0 -1
- data/definitions/features/mongo.rb +51 -12
- data/definitions/features/pulp.rb +2 -1
- data/definitions/features/service.rb +36 -0
- data/definitions/features/tar.rb +16 -4
- data/definitions/procedures/backup/online/pg_global_objects.rb +3 -1
- data/definitions/procedures/backup/prepare_directory.rb +1 -1
- data/definitions/procedures/backup/pulp.rb +2 -1
- data/definitions/procedures/pulp/migrate.rb +21 -0
- data/definitions/procedures/restore/candlepin_dump.rb +30 -0
- data/definitions/procedures/restore/configs.rb +40 -0
- data/definitions/procedures/restore/confirmation.rb +17 -0
- data/definitions/procedures/restore/drop_databases.rb +39 -0
- data/definitions/procedures/restore/extract_files.rb +70 -0
- data/definitions/procedures/restore/foreman_dump.rb +30 -0
- data/definitions/procedures/restore/installer_reset.rb +39 -0
- data/definitions/procedures/restore/mongo_dump.rb +41 -0
- data/definitions/procedures/restore/pg_global_objects.rb +36 -0
- data/definitions/procedures/restore/postgres_owner.rb +18 -0
- data/definitions/procedures/selinux/set_file_security.rb +22 -0
- data/definitions/procedures/service/daemon_reload.rb +18 -0
- data/definitions/procedures/service/restart.rb +2 -2
- data/definitions/scenarios/restore.rb +91 -0
- data/lib/foreman_maintain/cli.rb +3 -1
- data/lib/foreman_maintain/cli/restore_command.rb +26 -0
- data/lib/foreman_maintain/concerns/base_database.rb +35 -1
- data/lib/foreman_maintain/concerns/system_helpers.rb +1 -5
- data/lib/foreman_maintain/reporter/cli_reporter.rb +2 -1
- data/lib/foreman_maintain/utils/backup.rb +225 -0
- data/lib/foreman_maintain/utils/command_runner.rb +4 -2
- data/lib/foreman_maintain/utils/mongo_core.rb +49 -0
- data/lib/foreman_maintain/version.rb +1 -1
- metadata +20 -2
data/lib/foreman_maintain/cli.rb
CHANGED
@@ -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
|
-
|
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
|
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
|