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.
- 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
|